Files
waggle-os/packages/server/tests/d11-datadir-tier.test.ts
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

132 lines
5.1 KiB
TypeScript

/**
* D11 (UX-Refactor P4) — dataDir resolution + startup tier read.
*
* - resolveDataDir: explicit option > WAGGLE_DATA_DIR env > ~/.waggle. Before
* D11, startService ignored the env var while local/index.ts, the
* marketplace installer, and memory-mcp honored it — a custom install split
* its state across two directories.
* - readTierFromDataDir: the startup log line's tier source — same
* config.json contract as GET /api/tier, fails closed to FREE.
*/
import { describe, it, expect, afterEach, vi } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import { startService, resolveDataDir } from '../src/local/service.js';
import { readTierFromDataDir } from '../src/middleware/assert-tier.js';
import type { FastifyInstance } from 'fastify';
function makeTmpDir(): string {
return fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-d11-test-'));
}
const ENV_KEY = 'WAGGLE_DATA_DIR';
describe('resolveDataDir (D11)', () => {
const envBefore = process.env[ENV_KEY];
afterEach(() => {
if (envBefore === undefined) delete process.env[ENV_KEY];
else process.env[ENV_KEY] = envBefore;
});
it('explicit option wins over the env var', () => {
process.env[ENV_KEY] = 'D:/somewhere/else';
expect(resolveDataDir('D:/explicit')).toBe('D:/explicit');
});
it('WAGGLE_DATA_DIR is honored when no option is given', () => {
process.env[ENV_KEY] = 'D:/from-env';
expect(resolveDataDir()).toBe('D:/from-env');
});
it('defaults to ~/.waggle with neither', () => {
delete process.env[ENV_KEY];
expect(resolveDataDir()).toBe(path.join(os.homedir(), '.waggle'));
});
it('a set-but-EMPTY env var falls through to the default (never an empty dataDir)', () => {
process.env[ENV_KEY] = '';
expect(resolveDataDir()).toBe(path.join(os.homedir(), '.waggle'));
});
});
describe('startService honors WAGGLE_DATA_DIR (D11 integration)', () => {
const envBefore = process.env[ENV_KEY];
let server: FastifyInstance | undefined;
let tmp: string | undefined;
afterEach(async () => {
if (envBefore === undefined) delete process.env[ENV_KEY];
else process.env[ENV_KEY] = envBefore;
if (server) await server.close();
server = undefined;
if (tmp) fs.rmSync(tmp, { recursive: true, force: true });
tmp = undefined;
});
it('boots against the env-pointed directory when no dataDir option is passed', async () => {
tmp = makeTmpDir();
const envDir = path.join(tmp, 'env-pointed');
process.env[ENV_KEY] = envDir;
const port = 4600 + Math.floor(Math.random() * 400);
const logSpy = vi.spyOn(console, 'log');
try {
const result = await startService({ port, skipLiteLLM: true });
server = result.server;
// The env-pointed dir got the install, ~/.waggle got nothing new from us.
expect(fs.existsSync(path.join(envDir, 'personal.mind'))).toBe(true);
expect(server.localConfig.dataDir).toBe(envDir);
// The ratified D11 deliverable is the LOG LINE itself — pin it (review
// gap: deleting the line kept every gate green).
const dataDirLine = logSpy.mock.calls
.map((c) => String(c[0]))
.find((m) => m.includes('[waggle:service] Data dir:'));
expect(dataDirLine).toContain(envDir);
expect(dataDirLine).toMatch(/· tier: (TRIAL|FREE|TEAMS|ENTERPRISE)$/);
} finally {
logSpy.mockRestore();
}
});
});
describe('readTierFromDataDir (D11 startup log tier source)', () => {
let tmp: string;
afterEach(() => { fs.rmSync(tmp, { recursive: true, force: true }); });
function writeConfig(obj: Record<string, unknown>): string {
tmp = makeTmpDir();
fs.writeFileSync(path.join(tmp, 'config.json'), JSON.stringify(obj));
return tmp;
}
it('reads a canonical tier', () => {
expect(readTierFromDataDir(writeConfig({ tier: 'TEAMS' }))).toBe('TEAMS');
});
it('migrates legacy names (solo/basic/pro all → FREE after the Solo/Team collapse)', () => {
expect(readTierFromDataDir(writeConfig({ tier: 'solo' }))).toBe('FREE');
fs.rmSync(tmp, { recursive: true, force: true });
expect(readTierFromDataDir(writeConfig({ tier: 'basic' }))).toBe('FREE');
fs.rmSync(tmp, { recursive: true, force: true });
expect(readTierFromDataDir(writeConfig({ tier: 'pro' }))).toBe('FREE');
});
it('TRIAL downgrades to FREE when expired (effective tier, not raw)', () => {
const expired = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
expect(readTierFromDataDir(writeConfig({ tier: 'TRIAL', trialStartedAt: expired }))).toBe('FREE');
});
it('fails closed to FREE on missing dir, missing config, malformed JSON, unknown tier', () => {
expect(readTierFromDataDir(undefined)).toBe('FREE');
expect(readTierFromDataDir(path.join(os.tmpdir(), 'waggle-d11-nonexistent'))).toBe('FREE');
tmp = makeTmpDir();
fs.writeFileSync(path.join(tmp, 'config.json'), '{not json');
expect(readTierFromDataDir(tmp)).toBe('FREE');
fs.writeFileSync(path.join(tmp, 'config.json'), JSON.stringify({ tier: 'PLATINUM' }));
expect(readTierFromDataDir(tmp)).toBe('FREE');
});
});