@@ -35,6 +35,160 @@ public function set_up(): void {
3535 parent ::set_up ();
3636 }
3737
38+ /**
39+ * Tear down.
40+ */
41+ public function tear_down (): void {
42+ remove_all_filters ( 'pre_http_request ' );
43+ delete_transient ( 'iawmlf_archive_api_online ' );
44+ parent ::tear_down ();
45+ }
46+
47+ /**
48+ * Mocks the event's archive.org calls; livewebcheck returns $link_check_code.
49+ */
50+ private function mock_archive_http ( int $ link_check_code ): void {
51+ $ snapshot_body = json_encode (
52+ array (
53+ 'url ' => 'http://example.com ' ,
54+ 'archived_snapshots ' => array (
55+ 'closest ' => array (
56+ 'available ' => true ,
57+ 'status ' => 200 ,
58+ 'url ' => 'http://web.archive.org/web/20240101000000/http://example.com ' ,
59+ 'timestamp ' => '20240101000000 ' ,
60+ ),
61+ ),
62+ )
63+ );
64+
65+ add_filter (
66+ 'pre_http_request ' ,
67+ function ( $ pre , $ args , $ url ) use ( $ link_check_code , $ snapshot_body ) {
68+ // The system status endpoint (is_online).
69+ if ( false !== strpos ( $ url , 'save/status/system ' ) ) {
70+ return array (
71+ 'response ' => array ( 'code ' => 200 ),
72+ 'body ' => json_encode ( array ( 'status ' => 'ok ' ) ),
73+ );
74+ }
75+
76+ // The find-snapshot endpoint (get_latest_snapshot).
77+ if ( false !== strpos ( $ url , 'wayback/available ' ) ) {
78+ return array (
79+ 'response ' => array ( 'code ' => 200 ),
80+ 'body ' => $ snapshot_body ,
81+ );
82+ }
83+
84+ // The link checker endpoint (check_single) - the one under test.
85+ if ( false !== strpos ( $ url , 'livewebcheck ' ) ) {
86+ return array (
87+ 'response ' => array ( 'code ' => $ link_check_code ),
88+ 'body ' => json_encode ( array ( 'status ' => 200 ) ),
89+ );
90+ }
91+
92+ // Anything else, a benign 200.
93+ return array (
94+ 'response ' => array ( 'code ' => 200 ),
95+ 'body ' => '{} ' ,
96+ );
97+ },
98+ 10 ,
99+ 3
100+ );
101+ }
102+
103+ /**
104+ * @testdox BASELINE: when the link checker returns a 200, the link is checked and marked done.
105+ *
106+ * @return void
107+ */
108+ public function test_200_from_link_checker_processes_link (): void {
109+ $ this ->mock_archive_http ( 200 );
110+ delete_transient ( 'iawmlf_archive_api_online ' );
111+
112+ $ repo = new Link_Repository ();
113+ $ link = $ repo ->upsert ( new Link ( 'https://example.com ' ) );
114+
115+ $ event = new Find_Or_Create_Snapshot_Event ();
116+ $ event ->setup ();
117+ $ event ( $ link ->get_id () );
118+
119+ $ link = $ repo ->find_by_id ( $ link ->get_id () );
120+
121+ // A successful check means the link is processed/done.
122+ $ this ->assertEquals ( Link::PROCESS_DONE , $ link ->get_archive_process () );
123+ }
124+
125+ /**
126+ * @testdox When a link-check HTTP request comes back as a non-200, the event must do nothing - the link is left untouched.
127+ *
128+ * @dataProvider non_success_status_code_provider
129+ *
130+ * @param int $code The HTTP status code the livewebcheck endpoint returns.
131+ *
132+ * @return void
133+ */
134+ public function test_non_2xx_from_link_checker_does_nothing ( int $ code ): void {
135+ $ this ->mock_archive_http ( $ code );
136+ delete_transient ( 'iawmlf_archive_api_online ' );
137+
138+ $ repo = new Link_Repository ();
139+ $ link = $ repo ->upsert ( new Link ( 'https://example.com ' ) );
140+ $ id = $ link ->get_id ();
141+
142+ // Capture the state before the event runs.
143+ $ process_before = $ link ->get_archive_process ();
144+
145+ $ event = new Find_Or_Create_Snapshot_Event ();
146+ $ event ->setup ();
147+
148+ try {
149+ $ event ( $ id );
150+ } catch ( \Throwable $ e ) {
151+ // "Do nothing" means it must not bubble an error to the scheduler either.
152+ $ this ->fail ( "A {$ code } link-check response must be handled silently, but the event threw: " . $ e ->getMessage () );
153+ }
154+
155+ $ link = $ repo ->find_by_id ( $ id );
156+
157+ // The links process state must be unchanged.
158+ $ this ->assertSame (
159+ $ process_before ,
160+ $ link ->get_archive_process (),
161+ "A {$ code } link-check response must not change the link's process state. "
162+ );
163+
164+ // The link must not have been marked done.
165+ $ this ->assertNotEquals (
166+ Link::PROCESS_DONE ,
167+ $ link ->get_archive_process (),
168+ "A {$ code } link-check response must not mark the link as done. "
169+ );
170+
171+ // The link must not have had an archived url applied.
172+ $ this ->assertEmpty (
173+ $ link ->get_archived_href (),
174+ "A {$ code } link-check response must not set an archived url on the link. "
175+ );
176+ }
177+
178+ /**
179+ * Non-2xx (offline) HTTP status codes seen during the DDoS outage.
180+ *
181+ * @return array<string, array{0:int}>
182+ */
183+ public static function non_success_status_code_provider (): array {
184+ return array (
185+ '503 service unavailable ' => array ( 503 ),
186+ '404 not found ' => array ( 404 ),
187+ '403 forbidden ' => array ( 403 ),
188+ '500 generic non-2xx ' => array ( 500 ),
189+ );
190+ }
191+
38192 /**
39193 * @testdox When a link is added to the queue and its url is already an archive.org url (HTTPS), it should not be added to the queue and a message added to the link object.
40194 *
0 commit comments