moving
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled

This commit is contained in:
Oleg Maslov
2026-09-02 10:10:29 +02:00
commit 0c3e2ead3b
3841 changed files with 970576 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
/**
* P4 (UX-Refactor) — server-authoritative onboarding status.
*
* THE clean-install pin: a freshly-booted server (ensureDefault already seeded
* the default workspace, personal mind empty, no flag) must report
* completed:false — the workspace-count heuristic this replaces reported the
* seeded stub as a "returning user" and silently skipped the wizard for every
* brand-new production install (S4 founder flag, confirmed in P4).
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import Fastify from 'fastify';
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import { MindDB, FrameStore, SessionStore } from '@waggle/core';
import { onboardingRoutes } from '../../src/local/routes/onboarding.js';
function createTestServer(dataDir: string, db: MindDB, workspaces: Array<{ id: string }>) {
const server = Fastify({ logger: false });
server.decorate('localConfig', { dataDir });
server.decorate('multiMind', { personal: db });
server.decorate('workspaceManager', { list: () => workspaces });
server.register(onboardingRoutes);
return server;
}
describe('GET/POST /api/onboarding (P4 clean-install fix)', () => {
let tmp: string;
let db: MindDB;
let server: ReturnType<typeof Fastify>;
// The boot-seeded default workspace is ALWAYS present (ensureDefault).
const seededOnly = [{ id: 'default-workspace' }];
beforeEach(() => {
tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-onb-test-'));
db = new MindDB(':memory:');
});
afterEach(async () => {
await server.close();
db.close();
fs.rmSync(tmp, { recursive: true, force: true });
});
it('CLEAN INSTALL: seeded default workspace + empty mind + no flag → completed:false', async () => {
server = createTestServer(tmp, db, seededOnly);
const res = await server.inject({ method: 'GET', url: '/api/onboarding/status' });
expect(res.statusCode).toBe(200);
expect(res.json()).toEqual({ completed: false, source: 'none' });
});
it('completion flag → completed:true (the POST stamp round-trip)', async () => {
server = createTestServer(tmp, db, seededOnly);
const post = await server.inject({ method: 'POST', url: '/api/onboarding/complete' });
expect(post.statusCode).toBe(200);
expect(fs.existsSync(path.join(tmp, 'first-launch.flag'))).toBe(true);
const res = await server.inject({ method: 'GET', url: '/api/onboarding/status' });
expect(res.json()).toEqual({ completed: true, source: 'flag' });
});
it('legacy evidence: any frame in the personal mind → completed:true', async () => {
const sessions = new SessionStore(db);
const gop = sessions.create().gop_id;
new FrameStore(db).createIFrame(gop, 'Real prior usage.', 'normal');
server = createTestServer(tmp, db, seededOnly);
const res = await server.inject({ method: 'GET', url: '/api/onboarding/status' });
expect(res.json()).toEqual({ completed: true, source: 'legacy-evidence' });
});
it('legacy evidence: user-created workspaces beyond the seeded default → completed:true', async () => {
server = createTestServer(tmp, db, [...seededOnly, { id: 'my-real-project' }]);
const res = await server.inject({ method: 'GET', url: '/api/onboarding/status' });
expect(res.json()).toEqual({ completed: true, source: 'legacy-evidence' });
});
it('POST is idempotent', async () => {
server = createTestServer(tmp, db, seededOnly);
await server.inject({ method: 'POST', url: '/api/onboarding/complete' });
const second = await server.inject({ method: 'POST', url: '/api/onboarding/complete' });
expect(second.statusCode).toBe(200);
expect(second.json()).toEqual({ completed: true });
});
it('PENDING LATCH: wizard-origin writes after the first status call never flip it (review chain)', async () => {
server = createTestServer(tmp, db, seededOnly);
// First status call on the clean install stamps the pending latch.
const first = await server.inject({ method: 'GET', url: '/api/onboarding/status' });
expect(first.json()).toEqual({ completed: false, source: 'none' });
expect(fs.existsSync(path.join(tmp, 'onboarding-pending.flag'))).toBe(true);
// The wizard's step-1 profile write (PUT /api/profile → 'User identity:'
// frame) — the exact pre-completion evidence the review verified. With
// the latch, it must NOT turn the user into a "returning user".
const sessions = new SessionStore(db);
const gop = sessions.create().gop_id;
new FrameStore(db).createIFrame(gop, 'User identity: Name: New User', 'important');
const after = await server.inject({ method: 'GET', url: '/api/onboarding/status' });
expect(after.json()).toEqual({ completed: false, source: 'pending' });
// Completion clears the latch and stamps the flag.
await server.inject({ method: 'POST', url: '/api/onboarding/complete' });
expect(fs.existsSync(path.join(tmp, 'onboarding-pending.flag'))).toBe(false);
const done = await server.inject({ method: 'GET', url: '/api/onboarding/status' });
expect(done.json()).toEqual({ completed: true, source: 'flag' });
});
it('the latch never fires for a REAL returning user (evidence wins on first contact)', async () => {
const sessions = new SessionStore(db);
const gop = sessions.create().gop_id;
new FrameStore(db).createIFrame(gop, 'Genuine prior usage.', 'normal');
server = createTestServer(tmp, db, seededOnly);
const res = await server.inject({ method: 'GET', url: '/api/onboarding/status' });
expect(res.json()).toEqual({ completed: true, source: 'legacy-evidence' });
expect(fs.existsSync(path.join(tmp, 'onboarding-pending.flag'))).toBe(false);
});
it('registration pin: the real server wires onboardingRoutes (drop = red)', () => {
const indexSrc = fs.readFileSync(
path.resolve(import.meta.dirname, '..', '..', 'src', 'local', 'index.ts'),
'utf-8',
);
expect(indexSrc).toContain("import { onboardingRoutes } from './routes/onboarding.js'");
expect(indexSrc).toContain('server.register(onboardingRoutes)');
});
});