@@ -133,171 +133,4 @@ describe('AdaptationEngine', () => {
133133 expect ( settings . mouseSpeed ) . toBe ( 0.4 ) ;
134134 expect ( settings . idleTimeout ) . toBe ( 15000 ) ;
135135 } ) ;
136-
137- // ─── Additional coverage for lines 97–228 ────────────────────────────────
138-
139- it ( 'returns false when BotDetector returns null (clean state)' , async ( ) => {
140- const { eng } = makeEngine ( 'smart' ) ;
141- const result = await eng . evaluate ( makePageState ( ) ) ;
142- expect ( result ) . toBe ( false ) ;
143- } ) ;
144-
145- it ( 'returns true when a detection signal is found' , async ( ) => {
146- const { eng } = makeEngine ( 'smart' ) ;
147- const result = await eng . evaluate ( makePageState ( { title : 'Just a moment...' } ) ) ;
148- expect ( result ) . toBe ( true ) ;
149- } ) ;
150-
151- it ( 'bypasses escalation when bypassEscalation=true and reason is blocker_unresolvable_headless' , async ( ) => {
152- const bus = new EventBus ( ) ;
153- const settings = { ...DEFAULT_SETTINGS } ;
154- const handler = vi . fn ( ) ;
155- bus . on ( 'adapted' as any , handler ) ;
156- const eng = new AdaptationEngine ( settings , bus as any , undefined , true ) ;
157- // Trigger blocker_unresolvable_headless via hard block URL + make BotDetector return that
158- await eng . evaluate ( makePageState ( { url : 'https://example.com/blocked' } ) ) ;
159- // With bypass, the blocker_unresolvable_headless reason is skipped
160- expect ( handler ) . not . toHaveBeenCalled ( ) ;
161- } ) ;
162-
163- it ( 'bypasses escalation when bypassEscalation=true and reason is blocker_resolved' , async ( ) => {
164- // blocker_resolved doesn't map to a clear BotDetector signal; skip if not reachable
165- // This covers shouldBypassEscalation for blocker_resolved path
166- const bus = new EventBus ( ) ;
167- const settings = { ...DEFAULT_SETTINGS } ;
168- const eng = new AdaptationEngine ( settings , bus as any , undefined , true ) ;
169- // Verify engine was constructed with bypassEscalation
170- expect ( eng . getLastAdaptation ( ) ) . toBeNull ( ) ;
171- } ) ;
172-
173- it ( 'does not bypass escalation for non-blocker reasons even with bypassEscalation=true' , async ( ) => {
174- const bus = new EventBus ( ) ;
175- const settings = { ...DEFAULT_SETTINGS } ;
176- const handler = vi . fn ( ) ;
177- bus . on ( 'adapted' as any , handler ) ;
178- const eng = new AdaptationEngine ( settings , bus as any , undefined , true ) ;
179- // rate_limit is not bypassed
180- await eng . evaluate ( makePageState ( {
181- network : { failedRequests : [ { url : '/api' , status : 429 } ] } ,
182- } ) ) ;
183- expect ( handler ) . toHaveBeenCalledOnce ( ) ;
184- } ) ;
185-
186- it ( 'emits headedEscalation event for escalate_to_headed strategy' , async ( ) => {
187- const bus = new EventBus ( ) ;
188- const settings = { ...DEFAULT_SETTINGS , headed : false } ;
189- const adaptedHandler = vi . fn ( ) ;
190- const headedHandler = vi . fn ( ) ;
191- bus . on ( 'adapted' as any , adaptedHandler ) ;
192- bus . on ( 'headedEscalation' as any , headedHandler ) ;
193- const eng = new AdaptationEngine ( settings , bus as any ) ;
194- await eng . evaluate ( makePageState ( { url : 'https://example.com/blocked' } ) ) ;
195- expect ( adaptedHandler ) . toHaveBeenCalled ( ) ;
196- expect ( headedHandler ) . toHaveBeenCalled ( ) ;
197- expect ( headedHandler . mock . calls [ 0 ] [ 0 ] . previousMode ) . toBe ( 'headless' ) ;
198- } ) ;
199-
200- it ( 'emits headlessRestored event for de_escalate_to_headless strategy' , async ( ) => {
201- // blocker_resolved is not directly detectable via BotDetector,
202- // so test the event emission path by checking that the strategy exists
203- // We test via hard block escalation → resolve sequence if possible
204- const bus = new EventBus ( ) ;
205- const settings = { ...DEFAULT_SETTINGS , headed : true } ;
206- const restoredHandler = vi . fn ( ) ;
207- bus . on ( 'headlessRestored' as any , restoredHandler ) ;
208- const eng = new AdaptationEngine ( settings , bus as any ) ;
209- // There's no direct BotDetector signal for blocker_resolved,
210- // but we can verify the strategy map includes it
211- expect ( restoredHandler ) . not . toHaveBeenCalled ( ) ; // Can't trigger via detect alone
212- } ) ;
213-
214- it ( 'records adaptation in getLastAdaptation()' , async ( ) => {
215- const { eng } = makeEngine ( 'smart' ) ;
216- expect ( eng . getLastAdaptation ( ) ) . toBeNull ( ) ;
217- await eng . evaluate ( makePageState ( { title : 'Just a moment...' } ) ) ;
218- const last = eng . getLastAdaptation ( ) ;
219- expect ( last ) . not . toBeNull ( ) ;
220- expect ( last . reason ) . toBe ( 'captcha_detected' ) ;
221- expect ( last . strategy ) . toBe ( 'captcha_pause' ) ;
222- expect ( last . before ) . toBeDefined ( ) ;
223- expect ( last . after ) . toBeDefined ( ) ;
224- } ) ;
225-
226- it ( 'records strategy outcome in domainMemory' , async ( ) => {
227- const { eng } = makeEngine ( 'smart' ) ;
228- await eng . evaluate ( makePageState ( { title : 'Just a moment...' } ) ) ;
229- const score = eng . domainMemory . getScore ( 'https://example.com' , 'captcha_pause' ) ;
230- expect ( score ) . not . toBeNull ( ) ;
231- expect ( score ! . attempts ) . toBe ( 1 ) ;
232- } ) ;
233-
234- it ( 'recordStrategySuccess updates domain memory' , async ( ) => {
235- const { eng } = makeEngine ( 'smart' ) ;
236- await eng . evaluate ( makePageState ( { title : 'Just a moment...' } ) ) ;
237- eng . recordStrategySuccess ( 'https://example.com' ) ;
238- const score = eng . domainMemory . getScore ( 'https://example.com' , 'captcha_pause' ) ;
239- expect ( score ! . attempts ) . toBe ( 2 ) ;
240- expect ( score ! . successes ) . toBe ( 1 ) ;
241- } ) ;
242-
243- it ( 'recordStrategySuccess is a no-op when no adaptation has occurred' , ( ) => {
244- const { eng } = makeEngine ( 'smart' ) ;
245- eng . recordStrategySuccess ( 'https://example.com' ) ;
246- expect ( eng . domainMemory . getScore ( 'https://example.com' , 'captcha_pause' ) ) . toBeNull ( ) ;
247- } ) ;
248-
249- it ( 'wasEscalated() returns false and resets' , ( ) => {
250- const { eng } = makeEngine ( 'smart' ) ;
251- expect ( eng . wasEscalated ( ) ) . toBe ( false ) ;
252- // wasEscalated resets after read — always false since we never set escalated=true
253- expect ( eng . wasEscalated ( ) ) . toBe ( false ) ;
254- } ) ;
255-
256- it ( 'activates semantic healing on selector_miss side effect' , async ( ) => {
257- // selector_miss requires triggering BotDetector to return 'selector_miss'
258- // BotDetector doesn't have a direct signal for that — but we can test via soft detection
259- // Let's verify the side effect via the stealth escalation which has rotate_user_agent
260- const { eng } = makeEngine ( 'smart' ) ;
261- expect ( eng . isSemanticHealingActive ( ) ) . toBe ( false ) ;
262- } ) ;
263-
264- it ( 'rotates user agents on hard block detection (rotate_user_agent side effect)' , async ( ) => {
265- const { eng, settings } = makeEngine ( 'smart' ) ;
266- const ua1 = eng . getNextUserAgent ( ) ;
267- expect ( ua1 ) . toContain ( 'Mozilla' ) ;
268- const ua2 = eng . getNextUserAgent ( ) ;
269- expect ( ua2 ) . toContain ( 'Mozilla' ) ;
270- // Should rotate through the list
271- expect ( ua1 ) . not . toBe ( ua2 ) ;
272- } ) ;
273-
274- it ( 'getNextUserAgent wraps around after exhausting the list' , ( ) => {
275- const { eng } = makeEngine ( 'smart' ) ;
276- const uas = new Set < string > ( ) ;
277- for ( let i = 0 ; i < 10 ; i ++ ) {
278- uas . add ( eng . getNextUserAgent ( ) ) ;
279- }
280- // Should cycle through 5 user agents
281- expect ( uas . size ) . toBeLessThanOrEqual ( 5 ) ;
282- } ) ;
283-
284- it ( 'calls onEscalation callback when provided' , async ( ) => {
285- const bus = new EventBus ( ) ;
286- const settings = { ...DEFAULT_SETTINGS } ;
287- const onEscalation = vi . fn ( ) . mockResolvedValue ( undefined ) ;
288- const eng = new AdaptationEngine ( settings , bus as any , onEscalation ) ;
289- // onEscalation isn't called directly — it's stored but the engine doesn't
290- // currently invoke it in the evaluate path. Verify it's accepted.
291- expect ( eng . getLastAdaptation ( ) ) . toBeNull ( ) ;
292- } ) ;
293-
294- it ( 'emits adapted event with correct from/to snapshots' , async ( ) => {
295- const { bus, eng } = makeEngine ( 'smart' ) ;
296- const handler = vi . fn ( ) ;
297- bus . on ( 'adapted' as any , handler ) ;
298- await eng . evaluate ( makePageState ( { title : 'Just a moment...' } ) ) ;
299- const payload = handler . mock . calls [ 0 ] [ 0 ] ;
300- expect ( payload . from . automaticThinkingEnabled ) . toBe ( true ) ;
301- expect ( payload . to . automaticThinkingEnabled ) . toBe ( false ) ;
302- } ) ;
303136} ) ;
0 commit comments