📦 deps(thirdparty): update snapshots
This commit is contained in:
+236
@@ -0,0 +1,236 @@
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
const scriptPath = path.resolve(__dirname, '..', 'audit_search_migration_readiness.js');
|
||||
const { auditMigrationReadiness } = require(scriptPath);
|
||||
|
||||
function writeJson(filePath, data) {
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
function fixture({ currentProperties = true, legacyOnly = false, malformed = false } = {}) {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'migration-readiness-'));
|
||||
const current = 'https://example.github.io/agentic-awesome-skills/';
|
||||
const legacy = 'https://example.github.io/antigravity-awesome-skills/';
|
||||
fs.mkdirSync(path.join(root, 'apps/web-app/public'), { recursive: true });
|
||||
fs.writeFileSync(path.join(root, 'apps/web-app/public/sitemap.xml'), `<?xml version="1.0"?><urlset><url><loc>${current}</loc></url><url><loc>${current}plugins/</loc></url></urlset>`);
|
||||
writeJson(path.join(root, 'package.json'), { name: 'agentic-awesome-skills', version: '14.2.0' });
|
||||
writeJson(path.join(root, 'legacy-package.json'), { name: 'antigravity-awesome-skills', version: '13.13.0', deprecated: 'Moved to agentic-awesome-skills' });
|
||||
writeJson(path.join(root, 'redirects.json'), { redirects: [
|
||||
{ from: legacy, to: current }, { from: `${legacy}plugins/`, to: `${current}plugins/` },
|
||||
] });
|
||||
const property = legacyOnly ? legacy : current;
|
||||
const snapshot = new Date().toISOString().slice(0, 10);
|
||||
if (currentProperties || legacyOnly) {
|
||||
for (const filename of ['google-search-console.json', 'bing-webmaster-search-performance.json']) {
|
||||
const filePath = path.join(root, '.codex/traffic-snapshots', snapshot, filename);
|
||||
if (malformed) {
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
fs.writeFileSync(filePath, '{broken');
|
||||
} else {
|
||||
writeJson(filePath, {
|
||||
status: 'success',
|
||||
captured_at_utc: `${new Date().toISOString().slice(0, 10)}T12:00:00Z`,
|
||||
dashboard_url: filename.startsWith('google')
|
||||
? `https://search.google.com/search-console/performance?resource_id=${encodeURIComponent(property)}`
|
||||
: `https://www.bing.com/webmasters/searchperf?siteUrl=${encodeURIComponent(property)}`,
|
||||
totals: { clicks: 1, impressions: 10 },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return { root, current, legacy };
|
||||
}
|
||||
|
||||
function options(root) {
|
||||
return {
|
||||
repoRoot: root,
|
||||
redirectManifestPath: 'redirects.json',
|
||||
legacyPackagePath: 'legacy-package.json',
|
||||
currentPagesUrl: 'https://example.github.io/agentic-awesome-skills/',
|
||||
legacyPagesUrl: 'https://example.github.io/antigravity-awesome-skills/',
|
||||
currentPackageName: 'agentic-awesome-skills',
|
||||
asOfDate: new Date().toISOString().slice(0, 10),
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
const { root } = fixture();
|
||||
const snapshot = new Date().toISOString().slice(0, 10);
|
||||
for (const filename of ['google-search-console.json', 'bing-webmaster-search-performance.json']) {
|
||||
writeJson(path.join(root, '.codex/traffic-snapshots', snapshot, filename), {
|
||||
status: 'success',
|
||||
captured_at_utc: `${snapshot}T12:00:00Z`,
|
||||
dashboard_url: `https://attacker.example/?next=${encodeURIComponent('https://example.github.io/agentic-awesome-skills/')}`,
|
||||
totals: { clicks: 1, impressions: 10 },
|
||||
});
|
||||
}
|
||||
const report = auditMigrationReadiness(options(root));
|
||||
assert.strictEqual(report.status, 'not_ready', 'an unrelated URL containing the current property cannot pass');
|
||||
assert(report.failed_checks.includes('google_search_console'));
|
||||
}
|
||||
|
||||
{
|
||||
const { root, current, legacy } = fixture();
|
||||
const snapshot = new Date().toISOString().slice(0, 10);
|
||||
for (const filename of ['google-search-console.json', 'bing-webmaster-search-performance.json']) {
|
||||
writeJson(path.join(root, '.codex/traffic-snapshots', snapshot, filename), {
|
||||
status: 'success',
|
||||
captured_at_utc: `${snapshot}T12:00:00Z`,
|
||||
source_property: current,
|
||||
dashboard_url: filename.startsWith('google')
|
||||
? `https://search.google.com/search-console/performance?resource_id=${encodeURIComponent(legacy)}`
|
||||
: `https://www.bing.com/webmasters/searchperf?siteUrl=${encodeURIComponent(legacy)}`,
|
||||
totals: { clicks: 1, impressions: 10 },
|
||||
});
|
||||
}
|
||||
const report = auditMigrationReadiness(options(root));
|
||||
assert.strictEqual(report.status, 'not_ready', 'conflicting observed property signals cannot pass');
|
||||
assert.strictEqual(report.checks.google_search_console.rejected_current_evidence[0].property_conflict, true);
|
||||
}
|
||||
|
||||
{
|
||||
const { root, current } = fixture();
|
||||
const tomorrow = new Date(Date.now() + 86400000).toISOString().slice(0, 10);
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
for (const filename of ['google-search-console.json', 'bing-webmaster-search-performance.json']) {
|
||||
writeJson(path.join(root, '.codex/traffic-snapshots', tomorrow, filename), {
|
||||
status: 'success',
|
||||
captured_at_utc: `${tomorrow}T12:00:00Z`,
|
||||
dashboard_url: filename.startsWith('google')
|
||||
? `https://search.google.com/search-console/performance?resource_id=${encodeURIComponent(current)}`
|
||||
: `https://www.bing.com/webmasters/searchperf?siteUrl=${encodeURIComponent(current)}`,
|
||||
totals: { clicks: 1, impressions: 10 },
|
||||
});
|
||||
}
|
||||
fs.rmSync(path.join(root, '.codex/traffic-snapshots', today), { recursive: true, force: true });
|
||||
const report = auditMigrationReadiness(options(root));
|
||||
assert.strictEqual(report.status, 'not_ready', 'evidence after the as-of date cannot pass');
|
||||
assert.strictEqual(report.checks.google_search_console.rejected_current_evidence[0].freshness.reason, 'capture is in the future');
|
||||
}
|
||||
|
||||
{
|
||||
const { root, current } = fixture();
|
||||
const snapshot = new Date().toISOString().slice(0, 10);
|
||||
for (const filename of ['google-search-console.json', 'bing-webmaster-search-performance.json']) {
|
||||
writeJson(path.join(root, '.codex/traffic-snapshots', snapshot, filename), {
|
||||
status: 'success',
|
||||
captured_at_utc: `${snapshot}T12:00:00Z`,
|
||||
dashboard_url: `https://attacker.example/${filename}?${filename.startsWith('google') ? 'resource_id' : 'siteUrl'}=${encodeURIComponent(current)}`,
|
||||
totals: { clicks: 1, impressions: 10 },
|
||||
});
|
||||
}
|
||||
const report = auditMigrationReadiness(options(root));
|
||||
assert.strictEqual(report.status, 'not_ready', 'recognized property params on an attacker host cannot pass');
|
||||
assert.strictEqual(report.checks.google_search_console.rejected_current_evidence.length, 0);
|
||||
}
|
||||
|
||||
{
|
||||
const { root, current, legacy } = fixture();
|
||||
const snapshot = new Date().toISOString().slice(0, 10);
|
||||
for (const filename of ['google-search-console.json', 'bing-webmaster-search-performance.json']) {
|
||||
const base = filename.startsWith('google')
|
||||
? 'https://search.google.com/search-console/performance'
|
||||
: 'https://www.bing.com/webmasters/searchperf';
|
||||
writeJson(path.join(root, '.codex/traffic-snapshots', snapshot, filename), {
|
||||
status: 'success',
|
||||
captured_at_utc: `${snapshot}T12:00:00Z`,
|
||||
dashboard_url: `${base}?resource_id=${encodeURIComponent(current)}&siteUrl=${encodeURIComponent(legacy)}`,
|
||||
totals: { clicks: 1, impressions: 10 },
|
||||
});
|
||||
}
|
||||
const report = auditMigrationReadiness(options(root));
|
||||
assert.strictEqual(report.status, 'not_ready', 'conflicting recognized dashboard property params cannot pass');
|
||||
}
|
||||
|
||||
{
|
||||
const { root } = fixture();
|
||||
const snapshot = new Date().toISOString().slice(0, 10);
|
||||
for (const filename of ['google-search-console.json', 'bing-webmaster-search-performance.json']) {
|
||||
const filePath = path.join(root, '.codex/traffic-snapshots', snapshot, filename);
|
||||
const payload = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
payload.totals = { clicks: 0.5, impressions: 1.25 };
|
||||
writeJson(filePath, payload);
|
||||
}
|
||||
const report = auditMigrationReadiness(options(root));
|
||||
assert.strictEqual(report.status, 'not_ready', 'fractional count metrics cannot satisfy evidence completeness');
|
||||
}
|
||||
|
||||
{
|
||||
const { root } = fixture();
|
||||
const redirects = JSON.parse(fs.readFileSync(path.join(root, 'redirects.json'), 'utf8'));
|
||||
redirects.redirects.push(redirects.redirects[0]);
|
||||
writeJson(path.join(root, 'redirects.json'), redirects);
|
||||
const report = auditMigrationReadiness(options(root));
|
||||
assert.strictEqual(report.status, 'not_ready', 'duplicate redirects violate exact-once coverage');
|
||||
assert.strictEqual(report.checks.redirect_manifest_coverage.duplicates.length, 1);
|
||||
}
|
||||
|
||||
{
|
||||
const { root } = fixture();
|
||||
fs.writeFileSync(path.join(root, 'apps/web-app/public/sitemap.xml'), '<?xml version="1.0"?><urlset><url><loc>https://evil.example/not-the-project/</loc></url></urlset>');
|
||||
writeJson(path.join(root, 'package.json'), { name: 'unrelated-package', version: '1.0.0' });
|
||||
const report = auditMigrationReadiness({ ...options(root), currentPagesUrl: undefined, legacyPagesUrl: undefined, currentPackageName: undefined });
|
||||
assert.strictEqual(report.status, 'not_ready', 'defaults stay anchored to the real AAS identities');
|
||||
assert(report.failed_checks.includes('current_sitemap_identity'));
|
||||
assert(report.failed_checks.includes('npm_identities'));
|
||||
}
|
||||
|
||||
{
|
||||
const { root } = fixture();
|
||||
const report = auditMigrationReadiness(options(root));
|
||||
assert.strictEqual(report.status, 'ready');
|
||||
assert.deepStrictEqual(report.failed_checks, []);
|
||||
assert.strictEqual(report.checks.redirect_manifest_coverage.covered, 2);
|
||||
}
|
||||
|
||||
{
|
||||
const { root } = fixture({ currentProperties: false });
|
||||
const report = auditMigrationReadiness(options(root));
|
||||
assert.strictEqual(report.status, 'not_ready');
|
||||
assert(report.failed_checks.includes('google_search_console'));
|
||||
assert(report.failed_checks.includes('bing_webmaster'));
|
||||
}
|
||||
|
||||
{
|
||||
const { root } = fixture({ currentProperties: false, legacyOnly: true });
|
||||
const report = auditMigrationReadiness(options(root));
|
||||
assert.strictEqual(report.status, 'not_ready');
|
||||
assert.strictEqual(report.checks.google_search_console.legacy_evidence.length, 1);
|
||||
assert.strictEqual(report.checks.google_search_console.current_evidence.length, 0);
|
||||
}
|
||||
|
||||
{
|
||||
const { root } = fixture({ malformed: true });
|
||||
const report = auditMigrationReadiness(options(root));
|
||||
assert.strictEqual(report.status, 'not_ready');
|
||||
assert(report.errors.some((error) => error.includes('google-search-console.json')));
|
||||
}
|
||||
|
||||
{
|
||||
const { root } = fixture();
|
||||
const output = path.join(root, 'out.json');
|
||||
const command = [
|
||||
scriptPath,
|
||||
'--repo-root', root,
|
||||
'--redirect-manifest', 'redirects.json',
|
||||
'--legacy-package', 'legacy-package.json',
|
||||
'--current-pages-url', 'https://example.github.io/agentic-awesome-skills/',
|
||||
'--legacy-pages-url', 'https://example.github.io/antigravity-awesome-skills/',
|
||||
'--current-package-name', 'agentic-awesome-skills',
|
||||
'--as-of', new Date().toISOString().slice(0, 10),
|
||||
'--output', output,
|
||||
];
|
||||
const first = spawnSync(process.execPath, command, { encoding: 'utf8' });
|
||||
const firstFile = fs.readFileSync(output, 'utf8');
|
||||
const second = spawnSync(process.execPath, command, { encoding: 'utf8' });
|
||||
assert.strictEqual(first.status, 0, first.stderr);
|
||||
assert.strictEqual(second.status, 0, second.stderr);
|
||||
assert.strictEqual(fs.readFileSync(output, 'utf8'), firstFile);
|
||||
}
|
||||
|
||||
console.log('audit_search_migration_readiness tests passed');
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
const scriptPath = path.resolve(__dirname, '..', 'generate-pages-redirect-bridge.js');
|
||||
const { generateBridge } = require(scriptPath);
|
||||
|
||||
const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'pages-redirect-bridge-'));
|
||||
const sitemapPath = path.join(fixtureRoot, 'sitemap.xml');
|
||||
const current = 'https://example.github.io/agentic-awesome-skills/';
|
||||
const legacy = 'https://example.github.io/antigravity-awesome-skills/';
|
||||
|
||||
function sitemap(locations) {
|
||||
return `<?xml version="1.0"?><urlset>${locations.map((location) => `<url><loc>${location}</loc></url>`).join('')}</urlset>`;
|
||||
}
|
||||
|
||||
function readTree(root) {
|
||||
const result = {};
|
||||
function visit(directory) {
|
||||
for (const entry of fs.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
||||
const filePath = path.join(directory, entry.name);
|
||||
if (entry.isDirectory()) visit(filePath);
|
||||
else result[path.relative(root, filePath)] = fs.readFileSync(filePath, 'utf8');
|
||||
}
|
||||
}
|
||||
visit(root);
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
const locations = [current, `${current}plugins/`, `${current}topics/github-ai-skills-repository/`, `${current}skill/brainstorming/`];
|
||||
fs.writeFileSync(sitemapPath, sitemap(locations), 'utf8');
|
||||
const outputOne = path.join(fixtureRoot, '.codex', 'bridge-one');
|
||||
const manifest = generateBridge({
|
||||
repoRoot: fixtureRoot,
|
||||
sitemapPath,
|
||||
outputDirectory: outputOne,
|
||||
currentBase: current,
|
||||
legacyBase: legacy,
|
||||
expectedRoutes: 4,
|
||||
});
|
||||
assert.strictEqual(manifest.route_count, 4);
|
||||
assert.strictEqual(manifest.redirects.length, 4);
|
||||
assert.strictEqual(new Set(manifest.redirects.map((redirect) => redirect.from)).size, 4);
|
||||
assert.strictEqual(new Set(manifest.redirects.map((redirect) => redirect.output_file)).size, 4);
|
||||
|
||||
for (const relative of [
|
||||
'antigravity-awesome-skills/index.html',
|
||||
'antigravity-awesome-skills/plugins/index.html',
|
||||
'antigravity-awesome-skills/topics/github-ai-skills-repository/index.html',
|
||||
'antigravity-awesome-skills/skill/brainstorming/index.html',
|
||||
]) {
|
||||
assert(fs.existsSync(path.join(outputOne, relative)), `missing generated route: ${relative}`);
|
||||
}
|
||||
const pluginHtml = fs.readFileSync(path.join(outputOne, 'antigravity-awesome-skills/plugins/index.html'), 'utf8');
|
||||
assert.match(pluginHtml, /http-equiv="refresh" content="0; url=https:\/\/example\.github\.io\/agentic-awesome-skills\/plugins\/"/);
|
||||
assert.match(pluginHtml, /rel="canonical" href="https:\/\/example\.github\.io\/agentic-awesome-skills\/plugins\/"/);
|
||||
assert.match(pluginHtml, /<a href="https:\/\/example\.github\.io\/agentic-awesome-skills\/plugins\/">/);
|
||||
assert.strictEqual((fs.readFileSync(path.join(outputOne, 'antigravity-awesome-skills/sitemap.xml'), 'utf8').match(/<loc>/g) || []).length, 4);
|
||||
|
||||
const outputTwo = path.join(fixtureRoot, '.codex', 'bridge-two');
|
||||
generateBridge({
|
||||
repoRoot: fixtureRoot,
|
||||
sitemapPath,
|
||||
outputDirectory: outputTwo,
|
||||
currentBase: current,
|
||||
legacyBase: legacy,
|
||||
expectedRoutes: 4,
|
||||
});
|
||||
assert.deepStrictEqual(readTree(outputTwo), readTree(outputOne), 'identical input produces byte-identical output');
|
||||
|
||||
assert.throws(() => generateBridge({
|
||||
repoRoot: fixtureRoot,
|
||||
sitemapPath,
|
||||
outputDirectory: outputOne,
|
||||
currentBase: current,
|
||||
legacyBase: legacy,
|
||||
expectedRoutes: 4,
|
||||
}), /output path already exists/);
|
||||
|
||||
const foreignSitemap = path.join(fixtureRoot, 'foreign.xml');
|
||||
fs.writeFileSync(foreignSitemap, sitemap([current, 'https://attacker.example/skill/escape/']), 'utf8');
|
||||
assert.throws(() => generateBridge({
|
||||
repoRoot: fixtureRoot,
|
||||
sitemapPath: foreignSitemap,
|
||||
outputDirectory: path.join(fixtureRoot, '.codex', 'foreign'),
|
||||
currentBase: current,
|
||||
legacyBase: legacy,
|
||||
expectedRoutes: 2,
|
||||
}), /outside the current HTTPS identity/);
|
||||
|
||||
const duplicateSitemap = path.join(fixtureRoot, 'duplicate.xml');
|
||||
fs.writeFileSync(duplicateSitemap, sitemap([current, current]), 'utf8');
|
||||
assert.throws(() => generateBridge({
|
||||
repoRoot: fixtureRoot,
|
||||
sitemapPath: duplicateSitemap,
|
||||
outputDirectory: path.join(fixtureRoot, '.codex', 'duplicate'),
|
||||
currentBase: current,
|
||||
legacyBase: legacy,
|
||||
expectedRoutes: 2,
|
||||
}), /duplicate/);
|
||||
|
||||
const doubleEncodedSitemap = path.join(fixtureRoot, 'double-encoded.xml');
|
||||
fs.writeFileSync(doubleEncodedSitemap, sitemap([current, `${current}skill/&lt;escape/`]), 'utf8');
|
||||
assert.throws(() => generateBridge({
|
||||
repoRoot: fixtureRoot,
|
||||
sitemapPath: doubleEncodedSitemap,
|
||||
outputDirectory: path.join(fixtureRoot, '.codex', 'double-encoded'),
|
||||
currentBase: current,
|
||||
legacyBase: legacy,
|
||||
expectedRoutes: 2,
|
||||
}), /unsafe path segment/, 'XML entities must be decoded exactly once');
|
||||
|
||||
const trackedOutput = path.join(fixtureRoot, 'apps', 'web-app', 'public', 'bridge');
|
||||
assert.throws(() => generateBridge({
|
||||
repoRoot: fixtureRoot,
|
||||
sitemapPath,
|
||||
outputDirectory: trackedOutput,
|
||||
currentBase: current,
|
||||
legacyBase: legacy,
|
||||
expectedRoutes: 4,
|
||||
}), /only under ignored \.codex/);
|
||||
|
||||
const symlinkRepo = path.join(fixtureRoot, 'symlink-repo');
|
||||
const trackedPublic = path.join(symlinkRepo, 'apps', 'web-app', 'public');
|
||||
fs.mkdirSync(trackedPublic, { recursive: true });
|
||||
const symlinkSitemap = path.join(symlinkRepo, 'sitemap.xml');
|
||||
fs.writeFileSync(symlinkSitemap, sitemap(locations), 'utf8');
|
||||
fs.symlinkSync(trackedPublic, path.join(symlinkRepo, '.codex'));
|
||||
assert.throws(() => generateBridge({
|
||||
repoRoot: symlinkRepo,
|
||||
sitemapPath: symlinkSitemap,
|
||||
outputDirectory: path.join(symlinkRepo, '.codex', 'bridge'),
|
||||
currentBase: current,
|
||||
legacyBase: legacy,
|
||||
expectedRoutes: 4,
|
||||
}), /symlink|physical output/);
|
||||
assert(!fs.existsSync(path.join(trackedPublic, 'bridge')), 'a .codex symlink cannot redirect writes into tracked public files');
|
||||
|
||||
const outsideRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'pages-redirect-outside-'));
|
||||
try {
|
||||
fs.symlinkSync(trackedPublic, path.join(outsideRoot, 'linked-public'));
|
||||
assert.throws(() => generateBridge({
|
||||
repoRoot: symlinkRepo,
|
||||
sitemapPath: symlinkSitemap,
|
||||
outputDirectory: path.join(outsideRoot, 'linked-public', 'bridge'),
|
||||
currentBase: current,
|
||||
legacyBase: legacy,
|
||||
expectedRoutes: 4,
|
||||
}), /physical output resolves inside the repository/);
|
||||
assert(!fs.existsSync(path.join(trackedPublic, 'bridge')));
|
||||
} finally {
|
||||
fs.rmSync(outsideRoot, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
const sentinelDirectory = path.join(path.dirname(outputOne), `.${path.basename(outputOne)}.${process.pid}.sentinel`);
|
||||
fs.mkdirSync(sentinelDirectory);
|
||||
fs.writeFileSync(path.join(sentinelDirectory, 'KEEP'), 'owned by another process', 'utf8');
|
||||
const outputThree = path.join(fixtureRoot, '.codex', 'bridge-three');
|
||||
generateBridge({
|
||||
repoRoot: fixtureRoot,
|
||||
sitemapPath,
|
||||
outputDirectory: outputThree,
|
||||
currentBase: current,
|
||||
legacyBase: legacy,
|
||||
expectedRoutes: 4,
|
||||
});
|
||||
assert.strictEqual(fs.readFileSync(path.join(sentinelDirectory, 'KEEP'), 'utf8'), 'owned by another process');
|
||||
|
||||
const cliOutput = path.join(fixtureRoot, '.codex', 'cli');
|
||||
const cli = spawnSync(process.execPath, [
|
||||
scriptPath,
|
||||
'--sitemap', sitemapPath,
|
||||
'--output', cliOutput,
|
||||
'--current-base', current,
|
||||
'--legacy-base', legacy,
|
||||
'--expected-routes', '4',
|
||||
], { encoding: 'utf8' });
|
||||
assert.strictEqual(cli.status, 0, cli.stderr);
|
||||
assert.match(cli.stdout, /Generated 4 redirect pages/);
|
||||
|
||||
const missingOutput = spawnSync(process.execPath, [scriptPath, '--sitemap', sitemapPath], { encoding: 'utf8' });
|
||||
assert.strictEqual(missingOutput.status, 1);
|
||||
assert.match(missingOutput.stderr, /--output is required/);
|
||||
|
||||
console.log('generate_pages_redirect_bridge tests passed');
|
||||
} finally {
|
||||
fs.rmSync(fixtureRoot, { recursive: true, force: true });
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
const assert = require("assert");
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
const { spawnSync } = require("child_process");
|
||||
|
||||
const scriptPath = path.resolve(__dirname, "..", "normalize_traffic_snapshots.js");
|
||||
const { normalizeSnapshots } = require(scriptPath);
|
||||
|
||||
const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), "traffic-normalizer-"));
|
||||
|
||||
function writeJson(relativePath, value) {
|
||||
const filePath = path.join(fixtureRoot, relativePath);
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
fs.writeFileSync(filePath, JSON.stringify(value), "utf8");
|
||||
}
|
||||
|
||||
function writeManifest(date, repo = "owner/repo", timezone = "Europe/Rome") {
|
||||
writeJson(`${date}/manifest.json`, { repo, timezone });
|
||||
}
|
||||
|
||||
function dashboard(property, dailyValues, extra = {}) {
|
||||
return {
|
||||
status: "success",
|
||||
dashboard_url: `https://www.bing.com/webmasters/searchperf?siteUrl=${encodeURIComponent(property)}`,
|
||||
date_range_visible: "3 M (April 12, 2026 to July 10, 2026)",
|
||||
daily_values: dailyValues,
|
||||
totals: { clicks: 10, impressions: 100 },
|
||||
...extra,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
writeManifest("2026-07-01");
|
||||
writeJson("2026-07-01/views.json", {
|
||||
views: [{ timestamp: "2026-06-30T00:00:00Z", count: 1, uniques: 1 }],
|
||||
});
|
||||
writeJson("2026-07-01/clones.json", {
|
||||
clones: [{ timestamp: "2026-06-30T00:00:00Z", count: 2, uniques: 1 }],
|
||||
});
|
||||
writeJson("2026-07-01/bing-webmaster-search-performance.json", dashboard(
|
||||
"https://sickn33.github.io/antigravity-awesome-skills/",
|
||||
[{ date: "2026-06-30", clicks: 3, impressions: 30 }],
|
||||
));
|
||||
writeJson("2026-07-01/google-search-console.json", {
|
||||
status: "success",
|
||||
intended_property: "https://sickn33.github.io/antigravity-awesome-skills/",
|
||||
date_range_visible: "June 1, 2026 to July 9, 2026",
|
||||
totals: { clicks: 4, impressions: "1.5K" },
|
||||
});
|
||||
|
||||
writeManifest("2026-07-02");
|
||||
writeJson("2026-07-02/views.json", {
|
||||
views: [{ timestamp: "2026-06-30T00:00:00Z", count: 9, uniques: 8 }],
|
||||
});
|
||||
writeJson("2026-07-02/clones.json", {
|
||||
clones: [{ timestamp: "2026-06-30T00:00:00Z", count: 7, uniques: 6 }],
|
||||
});
|
||||
writeJson("2026-07-02/bing-webmaster-search-performance.json", dashboard(
|
||||
"https://sickn33.github.io/agentic-awesome-skills/",
|
||||
[{ date: "2026-06-30", clicks: 11, impressions: 110 }],
|
||||
));
|
||||
writeJson("2026-07-02/google-search-console.json", {
|
||||
status: "success",
|
||||
dashboard_url: "https://search.google.com/search-console/performance?resource_id=https%3A%2F%2Fsickn33.github.io%2Fagentic-awesome-skills%2F",
|
||||
date_range_visible: "June 1, 2026 to July 10, 2026",
|
||||
totals: { clicks: 5, impressions_visible: "2,5K" },
|
||||
});
|
||||
writeJson("2026-07-02/bing-webmaster-ai-performance.json", {
|
||||
status: "success",
|
||||
dashboard_url: "https://www.bing.com/webmasters/aiperformance?siteUrl=https%3A%2F%2Fsickn33.github.io%2Fagentic-awesome-skills%2F",
|
||||
daily_values: [{ date: "June 30, 2026", total_citations: 7, avg_cited_pages: 1 }],
|
||||
total_citations: 7,
|
||||
});
|
||||
|
||||
// A malformed file and partial rows must not leak values into valid rows.
|
||||
writeManifest("2026-07-03");
|
||||
fs.mkdirSync(path.join(fixtureRoot, "2026-07-03"), { recursive: true });
|
||||
fs.writeFileSync(path.join(fixtureRoot, "2026-07-03", "views.json"), "{not json", "utf8");
|
||||
writeJson("2026-07-03/clones.json", { clones: [{ timestamp: "2026-06-30T00:00:00Z", count: 99 }] });
|
||||
writeJson("2026-07-03/bing-webmaster-search-performance.json", dashboard(
|
||||
"https://sickn33.github.io/agentic-awesome-skills/",
|
||||
[{ date: "2026-06-30", impressions: 999 }],
|
||||
));
|
||||
|
||||
const normalized = normalizeSnapshots(fixtureRoot);
|
||||
assert.strictEqual(normalized.schema_version, "2.0.0");
|
||||
assert.strictEqual(normalized.repo, "owner/repo");
|
||||
assert.deepStrictEqual(normalized.source_repositories, ["owner/repo"]);
|
||||
assert.deepStrictEqual(normalized.snapshots, ["2026-07-01", "2026-07-02", "2026-07-03"]);
|
||||
const row = normalized.rows.find((value) => value.date === "2026-06-30");
|
||||
assert.strictEqual(row.github.views.count, 9, "latest GitHub view observation wins per date");
|
||||
assert.strictEqual(row.github.views.observed_from_snapshot, "2026-07-02");
|
||||
assert.strictEqual(row.github.clones.count, 7, "partial newer clone row cannot overwrite complete observation");
|
||||
assert.strictEqual(row.bing_search.length, 2, "different dashboard properties remain separate");
|
||||
assert.deepStrictEqual(row.bing_search.map((value) => value.property_identity), ["current", "legacy"]);
|
||||
assert.deepStrictEqual(row.bing_search.map((value) => value.coverage_end), ["2026-06-30", "2026-06-30"]);
|
||||
assert.deepStrictEqual(row.bing_search.map((value) => value.clicks), [11, 3]);
|
||||
assert.strictEqual(row.bing_ai[0].citations, 7, "human-readable compact Bing dates and total_citations are supported");
|
||||
|
||||
const gscTotals = normalized.snapshot_totals.google_search_console;
|
||||
assert.strictEqual(gscTotals.length, 2, "GSC legacy and current totals are never coalesced");
|
||||
assert.deepStrictEqual(gscTotals.map((value) => value.property_identity), ["current", "legacy"]);
|
||||
assert.deepStrictEqual(gscTotals.map((value) => value.coverage_end), ["2026-07-10", "2026-07-09"]);
|
||||
assert.deepStrictEqual(gscTotals.map((value) => value.impressions), [2500, 1500]);
|
||||
assert.ok(normalized.warnings.some((warning) => warning.includes("malformed JSON")));
|
||||
assert.ok(normalized.warnings.some((warning) => warning.includes("partial dashboard")));
|
||||
|
||||
writeManifest("2026-07-04", "attacker/other-repo", "Mars/Olympus");
|
||||
writeJson("2026-07-04/views.json", {
|
||||
views: [{ timestamp: "2026-06-30T00:00:00Z", count: 999, uniques: 999 }],
|
||||
});
|
||||
writeManifest("2026-07-05");
|
||||
writeJson("2026-07-05/views.json", {
|
||||
views: [{ timestamp: "2026-99-99T00:00:00Z", count: -4, uniques: 1 }],
|
||||
});
|
||||
writeJson("2026-07-05/google-search-console.json", {
|
||||
dashboard_url: "https://search.google.com/search-console/performance?resource_id=https%3A%2F%2Fsickn33.github.io%2Fagentic-awesome-skills%2F",
|
||||
totals: { clicks: 1 },
|
||||
});
|
||||
writeJson("2026-07-05/bing-webmaster-ai-performance.json", {
|
||||
status: "success",
|
||||
dashboard_url: "https://www.bing.com/webmasters/aiperformance?siteUrl=https%3A%2F%2Fsickn33.github.io%2Fagentic-awesome-skills%2F",
|
||||
date_range_visible: "2026-99-99 to 2026-99-99",
|
||||
total_citations: 5,
|
||||
});
|
||||
writeManifest("2026-07-06");
|
||||
writeJson("2026-07-06/google-search-console.json", {
|
||||
status: "success",
|
||||
intended_property: "https://sickn33.github.io/agentic-awesome-skills/",
|
||||
dashboard_url: "https://search.google.com/search-console/performance?resource_id=https%3A%2F%2Fsickn33.github.io%2Fantigravity-awesome-skills%2F",
|
||||
totals: { clicks: 8, impressions: 80 },
|
||||
});
|
||||
writeJson("2026-07-06/bing-webmaster-search-performance.json", {
|
||||
status: "success",
|
||||
dashboard_url: "https://attacker.example/webmasters/searchperf?siteUrl=https%3A%2F%2Fsickn33.github.io%2Fagentic-awesome-skills%2F",
|
||||
totals: { clicks: 8, impressions: 80 },
|
||||
});
|
||||
writeManifest("2026-07-07");
|
||||
writeJson("2026-07-07/google-search-console.json", {
|
||||
status: "success",
|
||||
dashboard_url: "https://search.google.com/search-console/performance?resource_id=https%3A%2F%2Fsickn33.github.io%2Fagentic-awesome-skills%2F",
|
||||
date_range_visible: "June 1, 2026 to July 10, 2026",
|
||||
daily_values: [{ date: "2026-06-29", clicks: 5, impressions: 50 }],
|
||||
totals: { clicks: 5, impressions: 50 },
|
||||
});
|
||||
writeManifest("2026-07-08");
|
||||
writeJson("2026-07-08/google-search-console.json", {
|
||||
status: "success",
|
||||
intended_property: "https://sickn33.github.io/agentic-awesome-skills/",
|
||||
date_range_visible: "June 1, 2026 to July 10, 2026",
|
||||
daily_values: [{ date: "2026-06-29", clicks: 999, impressions: 999 }],
|
||||
totals: { clicks: 999, impressions: 999 },
|
||||
});
|
||||
const adversarial = normalizeSnapshots(fixtureRoot);
|
||||
const stableRow = adversarial.rows.find((value) => value.date === "2026-06-30");
|
||||
assert.strictEqual(stableRow.github.views.count, 9, "mismatched repository snapshot cannot overwrite the series");
|
||||
assert(!adversarial.rows.some((value) => value.date === "2026-99-99"), "invalid calendar dates are rejected");
|
||||
assert(adversarial.warnings.some((warning) => warning.includes("repository or timezone mismatch")));
|
||||
assert(adversarial.warnings.some((warning) => warning.includes("non-success capture")));
|
||||
const invalidCoverageTotal = adversarial.snapshot_totals.bing_ai.find((value) => value.observed_from_snapshot === "2026-07-05");
|
||||
assert.strictEqual(invalidCoverageTotal.coverage_end, null, "impossible free-text coverage dates are rejected");
|
||||
const conflictedGsc = adversarial.snapshot_totals.google_search_console.find((value) => value.observed_from_snapshot === "2026-07-06");
|
||||
assert.strictEqual(conflictedGsc.property_identity, "legacy", "observed dashboard property wins over a conflicting intention");
|
||||
assert.strictEqual(conflictedGsc.property_provenance, "observed");
|
||||
assert(adversarial.warnings.some((warning) => warning.includes("intended property disagrees")));
|
||||
assert(!adversarial.snapshot_totals.bing_search.some((value) => value.observed_from_snapshot === "2026-07-06"), "attacker-host dashboard evidence is skipped");
|
||||
const preferredDaily = adversarial.rows.find((value) => value.date === "2026-06-29").google_search_console
|
||||
.find((value) => value.property_identity === "current");
|
||||
assert.strictEqual(preferredDaily.clicks, 5, "observed daily evidence cannot be overwritten by newer intended-only data");
|
||||
assert.strictEqual(preferredDaily.property_provenance, "observed");
|
||||
const preferredTotal = adversarial.snapshot_totals.google_search_console
|
||||
.find((value) => value.property_identity === "current" && value.coverage_end === "2026-07-10");
|
||||
assert.strictEqual(preferredTotal.clicks, 5, "observed totals cannot be overwritten by newer intended-only data");
|
||||
assert.strictEqual(preferredTotal.property_provenance, "observed");
|
||||
|
||||
const outputPath = path.join(fixtureRoot, "out", "daily-normalized.json");
|
||||
const first = spawnSync(process.execPath, [scriptPath, "--input", fixtureRoot, "--output", outputPath], { encoding: "utf8" });
|
||||
assert.strictEqual(first.status, 0, first.stderr);
|
||||
const firstOutput = fs.readFileSync(outputPath, "utf8");
|
||||
const second = spawnSync(process.execPath, [scriptPath, "--input", fixtureRoot, "--output", outputPath], { encoding: "utf8" });
|
||||
assert.strictEqual(second.status, 0, second.stderr);
|
||||
assert.strictEqual(fs.readFileSync(outputPath, "utf8"), firstOutput, "stable input produces stable output");
|
||||
|
||||
const missing = spawnSync(process.execPath, [scriptPath, "--input", path.join(fixtureRoot, "missing"), "--output", outputPath], { encoding: "utf8" });
|
||||
assert.strictEqual(missing.status, 1, "missing input is an operational failure");
|
||||
assert.match(missing.stderr, /input directory does not exist/);
|
||||
|
||||
const rejectedRoot = fs.mkdtempSync(path.join(os.tmpdir(), "traffic-normalizer-rejected-"));
|
||||
writeJson(path.relative(fixtureRoot, path.join(rejectedRoot, "2026-07-01", "manifest.json")), { repo: "owner/repo" });
|
||||
const allRejected = spawnSync(process.execPath, [scriptPath, "--input", rejectedRoot, "--output", path.join(rejectedRoot, "out.json")], { encoding: "utf8" });
|
||||
assert.strictEqual(allRejected.status, 1, "all-rejected snapshots are an operational failure");
|
||||
assert.match(allRejected.stderr, /no snapshots .* were accepted/);
|
||||
fs.rmSync(rejectedRoot, { recursive: true, force: true });
|
||||
|
||||
console.log("traffic snapshot normalizer tests passed.");
|
||||
} finally {
|
||||
fs.rmSync(fixtureRoot, { recursive: true, force: true });
|
||||
}
|
||||
Reference in New Issue
Block a user