11// Unit tests for the AEO axis. Pure functions + an injected fetch mock so the
22// check runner is fully deterministic (no network). Run with:
3- // node --test packages/core/test/aeo.test.js
4- import { test } from 'node:test' ;
5- import assert from 'node:assert/strict' ;
3+ // vitest run packages/core/test/aeo.test.js
4+ import { test , expect } from 'vitest' ;
65import {
76 AI_BOTS ,
87 AEO_CHECKS ,
98 detectAIBot ,
109 aiBotsBlockedByRobots ,
1110 toMarkdownUrl ,
1211 runAeoChecks ,
13- } from '../src/aeo.js ' ;
12+ } from '@slop-detect/core ' ;
1413
1514// ── detectAIBot ──────────────────────────────────────────────────────────────
1615test ( 'detectAIBot identifies known crawlers by substring, case-insensitive' , ( ) => {
1716 const gpt = detectAIBot ( 'Mozilla/5.0 (compatible; GPTBot/1.2; +https://openai.com/gptbot)' ) ;
18- assert . equal ( gpt . isBot , true ) ;
19- assert . equal ( gpt . vendor , 'OpenAI' ) ;
20- assert . equal ( gpt . purpose , 'training' ) ;
17+ expect ( gpt . isBot ) . toBe ( true ) ;
18+ expect ( gpt . vendor ) . toBe ( 'OpenAI' ) ;
19+ expect ( gpt . purpose ) . toBe ( 'training' ) ;
2120
2221 const claude = detectAIBot ( 'claudebot/1.0' ) ;
23- assert . equal ( claude . isBot , true ) ;
24- assert . equal ( claude . vendor , 'Anthropic' ) ;
22+ expect ( claude . isBot ) . toBe ( true ) ;
23+ expect ( claude . vendor ) . toBe ( 'Anthropic' ) ;
2524
2625 const human = detectAIBot ( 'Mozilla/5.0 (Macintosh) Safari/605' ) ;
27- assert . equal ( human . isBot , false ) ;
28- assert . equal ( human . vendor , null ) ;
26+ expect ( human . isBot ) . toBe ( false ) ;
27+ expect ( human . vendor ) . toBe ( null ) ;
2928
30- assert . equal ( detectAIBot ( '' ) . isBot , false ) ;
29+ expect ( detectAIBot ( '' ) . isBot ) . toBe ( false ) ;
3130} ) ;
3231
3332test ( 'AI_BOTS registry is non-trivial and covers the big four vendors' , ( ) => {
34- assert . ok ( AI_BOTS . length >= 20 , `expected a substantial registry, got ${ AI_BOTS . length } ` ) ;
33+ expect ( AI_BOTS . length >= 20 ) . toBeTruthy ( ) ;
3534 const vendors = new Set ( AI_BOTS . map ( ( b ) => b . vendor ) ) ;
3635 for ( const v of [ 'OpenAI' , 'Anthropic' , 'Perplexity' , 'Google' ] ) {
37- assert . ok ( vendors . has ( v ) , `registry missing ${ v } ` ) ;
36+ expect ( vendors . has ( v ) ) . toBeTruthy ( ) ;
3837 }
3938} ) ;
4039
4140// ── toMarkdownUrl ────────────────────────────────────────────────────────────
4241test ( 'toMarkdownUrl maps pages to their .md twin' , ( ) => {
43- assert . equal ( toMarkdownUrl ( 'https://x.com/blog/post' ) , 'https://x.com/blog/post.md' ) ;
44- assert . equal ( toMarkdownUrl ( 'https://x.com/' ) , 'https://x.com/index.md' ) ;
45- assert . equal ( toMarkdownUrl ( 'https://x.com/a/' ) , 'https://x.com/a.md' ) ;
42+ expect ( toMarkdownUrl ( 'https://x.com/blog/post' ) ) . toBe ( 'https://x.com/blog/post.md' ) ;
43+ expect ( toMarkdownUrl ( 'https://x.com/' ) ) . toBe ( 'https://x.com/index.md' ) ;
44+ expect ( toMarkdownUrl ( 'https://x.com/a/' ) ) . toBe ( 'https://x.com/a.md' ) ;
4645 // idempotent
47- assert . equal ( toMarkdownUrl ( 'https://x.com/a.md' ) , 'https://x.com/a.md' ) ;
46+ expect ( toMarkdownUrl ( 'https://x.com/a.md' ) ) . toBe ( 'https://x.com/a.md' ) ;
4847} ) ;
4948
5049// ── robots.txt parser ────────────────────────────────────────────────────────
5150test ( 'aiBotsBlockedByRobots: blanket Disallow under * blocks AI bots' , ( ) => {
5251 const r = aiBotsBlockedByRobots ( 'User-agent: *\nDisallow: /' , '/blog' ) ;
53- assert . equal ( r . blocked , true ) ;
54- assert . deepEqual ( r . agents , [ '*' ] ) ;
52+ expect ( r . blocked ) . toBe ( true ) ;
53+ expect ( r . agents ) . toEqual ( [ '*' ] ) ;
5554} ) ;
5655
5756test ( 'aiBotsBlockedByRobots: named AI bot disallow is detected' , ( ) => {
5857 const r = aiBotsBlockedByRobots (
5958 'User-agent: GPTBot\nDisallow: /\n\nUser-agent: *\nDisallow:' ,
6059 '/'
6160 ) ;
62- assert . equal ( r . blocked , true ) ;
63- assert . ok ( r . agents . includes ( 'gptbot' ) ) ;
61+ expect ( r . blocked ) . toBe ( true ) ;
62+ expect ( r . agents . includes ( 'gptbot' ) ) . toBeTruthy ( ) ;
6463} ) ;
6564
6665test ( 'aiBotsBlockedByRobots: empty Disallow and unrelated bots do not block' , ( ) => {
67- assert . equal ( aiBotsBlockedByRobots ( 'User-agent: *\nDisallow:' , '/' ) . blocked , false ) ;
68- assert . equal ( aiBotsBlockedByRobots ( 'User-agent: AhrefsBot\nDisallow: /' , '/' ) . blocked , false ) ;
69- assert . equal ( aiBotsBlockedByRobots ( '' , '/' ) . blocked , false ) ;
66+ expect ( aiBotsBlockedByRobots ( 'User-agent: *\nDisallow:' , '/' ) . blocked ) . toBe ( false ) ;
67+ expect ( aiBotsBlockedByRobots ( 'User-agent: AhrefsBot\nDisallow: /' , '/' ) . blocked ) . toBe ( false ) ;
68+ expect ( aiBotsBlockedByRobots ( '' , '/' ) . blocked ) . toBe ( false ) ;
7069} ) ;
7170
7271test ( 'aiBotsBlockedByRobots: path-scoped disallow only blocks matching paths' , ( ) => {
7372 const robots = 'User-agent: *\nDisallow: /admin' ;
74- assert . equal ( aiBotsBlockedByRobots ( robots , '/admin/users' ) . blocked , true ) ;
75- assert . equal ( aiBotsBlockedByRobots ( robots , '/blog/post' ) . blocked , false ) ;
73+ expect ( aiBotsBlockedByRobots ( robots , '/admin/users' ) . blocked ) . toBe ( true ) ;
74+ expect ( aiBotsBlockedByRobots ( robots , '/blog/post' ) . blocked ) . toBe ( false ) ;
7675} ) ;
7776
7877// ── runAeoChecks with an injected fetch mock ─────────────────────────────────
@@ -115,14 +114,10 @@ test('runAeoChecks: fully AEO-ready site scores AI-Ready (100)', async () => {
115114 } ) ;
116115
117116 const r = await runAeoChecks ( 'https://ready.example' , { fetchImpl, timeoutMs : 1000 } ) ;
118- assert . equal ( r . axis , 'aeo' ) ;
119- assert . equal (
120- r . score ,
121- 100 ,
122- `expected perfect, got ${ r . score } : ${ JSON . stringify ( r . failed . map ( ( c ) => c . id ) ) } `
123- ) ;
124- assert . equal ( r . tier , 'AI-Ready' ) ;
125- assert . equal ( r . requiredFailed , 0 ) ;
117+ expect ( r . axis ) . toBe ( 'aeo' ) ;
118+ expect ( r . score ) . toBe ( 100 ) ;
119+ expect ( r . tier ) . toBe ( 'AI-Ready' ) ;
120+ expect ( r . requiredFailed ) . toBe ( 0 ) ;
126121} ) ;
127122
128123test ( 'runAeoChecks: site that blocks GPTBot + has noindex fails the required checks' , async ( ) => {
@@ -142,14 +137,14 @@ test('runAeoChecks: site that blocks GPTBot + has noindex fails the required che
142137
143138 const r = await runAeoChecks ( 'https://blocked.example' , { fetchImpl, timeoutMs : 1000 } ) ;
144139 const failedIds = new Set ( r . failed . map ( ( c ) => c . id ) ) ;
145- assert . ok ( failedIds . has ( 'bot.notBlocked' ) , 'should flag blocked GPTBot' ) ;
146- assert . ok ( failedIds . has ( 'robots.aiAllowed' ) , 'should flag robots disallow' ) ;
147- assert . ok ( failedIds . has ( 'html.indexable' ) , 'should flag noindex' ) ;
148- assert . ok ( r . requiredFailed >= 3 ) ;
149- assert . equal ( r . tier , 'Invisible' ) ;
140+ expect ( failedIds . has ( 'bot.notBlocked' ) ) . toBeTruthy ( ) ;
141+ expect ( failedIds . has ( 'robots.aiAllowed' ) ) . toBeTruthy ( ) ;
142+ expect ( failedIds . has ( 'html.indexable' ) ) . toBeTruthy ( ) ;
143+ expect ( r . requiredFailed >= 3 ) . toBeTruthy ( ) ;
144+ expect ( r . tier ) . toBe ( 'Invisible' ) ;
150145} ) ;
151146
152147test ( 'AEO_CHECKS weights total 100' , ( ) => {
153148 const total = AEO_CHECKS . reduce ( ( s , c ) => s + c . weight , 0 ) ;
154- assert . equal ( total , 100 ) ;
149+ expect ( total ) . toBe ( 100 ) ;
155150} ) ;
0 commit comments