import { describe, expect, it } from 'vitest'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { assertManifest, analyzeSitemap, assertPrerenderedSkillRoutes, assertIndexSocialMeta, assertRobots, assertSitemap, extractSitemapLocations, } from './verify-seo-assets.js'; describe('seo assets verification helpers', () => { it('extracts sitemap location values in declaration order', () => { const xml = ` https://example.com/ https://example.com/skill/agent-a `; const locs = extractSitemapLocations(xml); expect(locs).toEqual([ 'https://example.com/', 'https://example.com/skill/agent-a', ]); }); it('validates a canonical sitemap with base path and enough top skills', () => { const xml = ` https://owner.github.io/repo/ https://owner.github.io/repo/skill/agent-a https://owner.github.io/repo/skill/agent-b `; expect(() => assertSitemap(xml, { minSkillUrls: 2 })).not.toThrow(); }); it('throws when sitemap has duplicated URLs', () => { const xml = ` https://example.com/ https://example.com/ `; expect(() => assertSitemap(xml)).toThrow('duplicated'); }); it('requires robots directives', () => { const robots = ` User-agent: * Allow: / Sitemap: https://example.com/sitemap.xml `; expect(() => assertRobots(robots)).not.toThrow(); }); it('requires social image tags in rendered index html', () => { const html = ` `; expect(() => assertIndexSocialMeta(html)).not.toThrow(); }); it('validates prerendered skill route files when present', () => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'seo-assets-')); const distDir = path.join(tmpDir, 'dist'); const routeFile = path.join(distDir, 'skill', 'agent-a', 'index.html'); fs.mkdirSync(path.dirname(routeFile), { recursive: true }); fs.writeFileSync(routeFile, ''); const xml = ` https://owner.github.io/repo/ https://owner.github.io/repo/skill/agent-a `; const report = analyzeSitemap(xml); expect(() => assertPrerenderedSkillRoutes(report.skillUrls, distDir, report.normalizedRootPath)).not.toThrow(); }); it('throws when a prerendered skill file is missing', () => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'seo-assets-')); const distDir = path.join(tmpDir, 'dist'); const xml = ` https://owner.github.io/repo/ https://owner.github.io/repo/skill/agent-a `; const report = analyzeSitemap(xml); expect(() => assertPrerenderedSkillRoutes(report.skillUrls, distDir, report.normalizedRootPath)).toThrow( 'Missing prerendered page for skill route', ); }); it('rejects missing social image tags', () => { const html = ` `; expect(() => assertIndexSocialMeta(html)).toThrow('twitter:image'); }); it('requires manifest identity and theme fields', () => { const manifest = JSON.stringify( { name: 'Antigravity', short_name: 'AG', theme_color: '#112233', description: 'desc', icons: [{ src: 'icon.svg' }], }, null, 2, ); expect(() => assertManifest(manifest)).not.toThrow(); }); });