109 lines
5.5 KiB
JavaScript
109 lines
5.5 KiB
JavaScript
import { describe, test, afterEach } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
mkdtempSync, rmSync, mkdirSync, writeFileSync, readFileSync, existsSync, statSync,
|
|
} from 'node:fs';
|
|
import { join, dirname } from 'node:path';
|
|
import { tmpdir } from 'node:os';
|
|
import { migrateData } from '../migrate-data.mjs';
|
|
|
|
function writeDeep(p, content) {
|
|
mkdirSync(dirname(p), { recursive: true });
|
|
writeFileSync(p, content);
|
|
}
|
|
|
|
describe('migrateData', () => {
|
|
let pluginRoot;
|
|
let dataRoot;
|
|
const savedData = process.env.LINKEDIN_STUDIO_DATA;
|
|
|
|
afterEach(() => {
|
|
for (const d of [pluginRoot, dataRoot]) {
|
|
if (d && existsSync(d)) rmSync(d, { recursive: true, force: true });
|
|
}
|
|
pluginRoot = undefined;
|
|
dataRoot = undefined;
|
|
if (savedData === undefined) delete process.env.LINKEDIN_STUDIO_DATA;
|
|
else process.env.LINKEDIN_STUDIO_DATA = savedData;
|
|
});
|
|
|
|
function setup({ withRuntime = false, withScaffolds = false } = {}) {
|
|
pluginRoot = mkdtempSync(join(tmpdir(), 'lis-plugin-'));
|
|
dataRoot = mkdtempSync(join(tmpdir(), 'lis-data-'));
|
|
process.env.LINKEDIN_STUDIO_DATA = dataRoot; // never the real $HOME
|
|
if (withRuntime) {
|
|
writeDeep(join(pluginRoot, 'assets/voice-samples/authentic-voice-samples.local.md'), 'REAL VOICE 227 lines');
|
|
writeDeep(join(pluginRoot, 'assets/drafts/queue.json'), '{"version":1,"queue":[]}');
|
|
writeDeep(join(pluginRoot, 'assets/analytics/content-history.md'), '# history');
|
|
writeDeep(join(pluginRoot, 'assets/analytics/exports/content-2026-W22.csv'), 'a,b\n1,2\n');
|
|
writeDeep(join(pluginRoot, 'assets/analytics/posts/2026-05-26.json'), '{"x":1}');
|
|
writeDeep(join(pluginRoot, 'assets/analytics/weekly-reports/2026-W22.json'), '{"w":22}');
|
|
}
|
|
if (withScaffolds) {
|
|
writeDeep(join(pluginRoot, 'assets/examples/high-engagement-posts.md'), 'MY REAL POST');
|
|
writeDeep(join(pluginRoot, 'assets/audience-insights/demographics.md'), 'demo');
|
|
writeDeep(join(pluginRoot, 'assets/audience-insights/engagement-patterns.md'), 'patterns');
|
|
writeDeep(join(pluginRoot, 'assets/templates/my-post-templates.md'), 'my templates');
|
|
}
|
|
}
|
|
|
|
test('(a) gitignored runtime files MOVED: source gone, dest byte-equal, voice drops .local, content-history moved', () => {
|
|
setup({ withRuntime: true });
|
|
migrateData({ pluginRoot });
|
|
|
|
assert.ok(!existsSync(join(pluginRoot, 'assets/voice-samples/authentic-voice-samples.local.md')), 'voice source gone');
|
|
assert.ok(!existsSync(join(pluginRoot, 'assets/drafts/queue.json')), 'queue source gone');
|
|
assert.ok(!existsSync(join(pluginRoot, 'assets/analytics/content-history.md')), 'content-history source gone');
|
|
assert.ok(!existsSync(join(pluginRoot, 'assets/analytics/exports/content-2026-W22.csv')), 'export source gone');
|
|
|
|
assert.equal(readFileSync(join(dataRoot, 'voice-samples/authentic-voice-samples.md'), 'utf-8'), 'REAL VOICE 227 lines');
|
|
assert.equal(readFileSync(join(dataRoot, 'drafts/queue.json'), 'utf-8'), '{"version":1,"queue":[]}');
|
|
assert.ok(existsSync(join(dataRoot, 'analytics/content-history.md')));
|
|
assert.ok(existsSync(join(dataRoot, 'analytics/exports/content-2026-W22.csv')));
|
|
assert.ok(existsSync(join(dataRoot, 'analytics/posts/2026-05-26.json')));
|
|
assert.ok(existsSync(join(dataRoot, 'analytics/weekly-reports/2026-W22.json')));
|
|
assert.ok(existsSync(join(dataRoot, '.migrated')), 'marker written');
|
|
});
|
|
|
|
test('(b) tracked scaffolds COPIED: external instance written, tracked source preserved', () => {
|
|
setup({ withScaffolds: true });
|
|
migrateData({ pluginRoot });
|
|
|
|
assert.ok(existsSync(join(pluginRoot, 'assets/examples/high-engagement-posts.md')), 'scaffold source preserved');
|
|
assert.ok(existsSync(join(pluginRoot, 'assets/templates/my-post-templates.md')), 'scaffold source preserved');
|
|
|
|
assert.equal(readFileSync(join(dataRoot, 'examples/high-engagement-posts.md'), 'utf-8'), 'MY REAL POST');
|
|
assert.equal(readFileSync(join(dataRoot, 'audience-insights/demographics.md'), 'utf-8'), 'demo');
|
|
assert.equal(readFileSync(join(dataRoot, 'audience-insights/engagement-patterns.md'), 'utf-8'), 'patterns');
|
|
assert.equal(readFileSync(join(dataRoot, 'templates/my-post-templates.md'), 'utf-8'), 'my templates');
|
|
});
|
|
|
|
test('(c) re-run is an idempotent no-op (.migrated short-circuits, no throw, dest unchanged)', () => {
|
|
setup({ withRuntime: true, withScaffolds: true });
|
|
migrateData({ pluginRoot });
|
|
const voiceDest = join(dataRoot, 'voice-samples/authentic-voice-samples.md');
|
|
const before = readFileSync(voiceDest, 'utf-8');
|
|
const mtimeBefore = statSync(voiceDest).mtimeMs;
|
|
|
|
const r2 = migrateData({ pluginRoot });
|
|
assert.equal(r2.status, 'already-migrated');
|
|
assert.equal(readFileSync(voiceDest, 'utf-8'), before);
|
|
assert.equal(statSync(voiceDest).mtimeMs, mtimeBefore);
|
|
});
|
|
|
|
test('(d) empty fixture (no user data) completes without error', () => {
|
|
setup({});
|
|
const r = migrateData({ pluginRoot });
|
|
assert.ok(r);
|
|
assert.ok(existsSync(join(dataRoot, '.migrated')));
|
|
});
|
|
|
|
test('(e) collision: existing external file kept, in-plugin source NOT clobbered', () => {
|
|
setup({ withRuntime: true });
|
|
writeDeep(join(dataRoot, 'voice-samples/authentic-voice-samples.md'), 'EXTERNAL CANONICAL');
|
|
migrateData({ pluginRoot });
|
|
|
|
assert.equal(readFileSync(join(dataRoot, 'voice-samples/authentic-voice-samples.md'), 'utf-8'), 'EXTERNAL CANONICAL');
|
|
assert.equal(readFileSync(join(pluginRoot, 'assets/voice-samples/authentic-voice-samples.local.md'), 'utf-8'), 'REAL VOICE 227 lines');
|
|
});
|
|
});
|