Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 12 additions & 15 deletions apps/desktop/src/main/__tests__/automation-persistence-e2e.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
/**
* End-to-end: durable cron persistence + cross-session query/management.
*
* Exercises the REAL host wiring (createMainAutomationWiring) against a REAL
* FileAutomationStore on a real temp workspace, simulating an app restart:
* Exercises the real host wiring against the operational SQLite authority,
* simulating an app restart:
*
* session A creates a durable cron ──sync──► <workspace>/automations.json
* │
* (restart: a fresh wiring loads it) ◄──loadAll────────┘
* │
* session A creates a durable cron ──sync──► runtime.sqlite
* (restart: a fresh wiring loads it) ◄──loadAll───────┘
* session B (never saw it) lists / pauses / resumes / deletes it
*
* This is the query-and-persistence loop the reviewer asked for: a persisted
Expand All @@ -18,10 +16,11 @@

import { strict as assert } from 'node:assert';
import { describe, it, before, after } from 'node:test';
import { mkdtemp, rm, readFile } from 'node:fs/promises';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import type { MakaToolContext, MakaTool } from '@maka/runtime';
import { createAutomationStore } from '@maka/storage';
import { createMainAutomationWiring } from '../automation-wiring.js';

function ctx(sessionId: string): MakaToolContext {
Expand Down Expand Up @@ -60,15 +59,13 @@ function automationTool(wiring: ReturnType<typeof makeWiring>): MakaTool {
}

async function readStore(workspaceRoot: string): Promise<Array<{ id: string; name: string }>> {
try {
const raw = await readFile(join(workspaceRoot, 'automations.json'), 'utf8');
return (JSON.parse(raw) as { automations: Array<{ id: string; name: string }> }).automations;
} catch {
return [];
}
return (await createAutomationStore(workspaceRoot).loadAll()).map(({ id, name }) => ({
id,
name,
}));
}

/** The store sync is fire-and-forget; poll the file until it settles. */
/** Store sync is fire-and-forget; poll the authority until it settles. */
async function waitForStore(
workspaceRoot: string,
predicate: (rows: Array<{ id: string; name: string }>) => boolean,
Expand Down Expand Up @@ -175,7 +172,7 @@ describe('E2E: durable cron persistence + cross-session query/management', () =>
});

describe('E2E: a cron-disabled host (CLI) sharing the workspace never clobbers durable crons', () => {
it('a heartbeat-only wiring neither loads nor overwrites the owner\'s automations.json', async () => {
it('a heartbeat-only wiring neither loads nor overwrites durable Automations', async () => {
const ws = await mkdtemp(join(tmpdir(), 'maka-automation-clobber-'));
try {
// ── owner (cron-enabled, desktop) creates a durable cron ──────────────
Expand Down
80 changes: 0 additions & 80 deletions apps/desktop/src/main/__tests__/credential-store-migration.test.ts

This file was deleted.

142 changes: 54 additions & 88 deletions apps/desktop/src/main/__tests__/daily-review-archive-store.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { strict as assert } from 'node:assert';
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, describe, it } from 'node:test';
import type { DailyReviewArchive } from '@maka/core';

import { createDailyReviewArchiveStore } from '../daily-review-archive-store.js';

const roots: string[] = [];
Expand All @@ -17,57 +16,10 @@ afterEach(async () => {
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
});

async function aRoot(): Promise<string> {
const root = await mkdtemp(join(tmpdir(), 'maka-daily-review-store-'));
roots.push(root);
return root;
}

function anArchive(id = '2026-08-03-1d'): DailyReviewArchive {
return {
id,
day: DAY,
range: 1,
status: 'ok',
generatedAt: 3,
trigger: 'manual',
modelKey: '',
sections: { summary: 'healthy' },
totals: { sessionCount: 1, requestCount: 2, totalTokens: 3, costUsd: 4, errorCount: 0 },
};
}

describe('Daily Review archive migration', () => {
it('reads a legacy deep archive through the range contract', async () => {
const root = await aRoot();
const archiveRoot = join(root, 'daily-reviews', 'archive');
await mkdir(archiveRoot, { recursive: true });
await writeFile(
join(archiveRoot, '2026-08-03-deep.json'),
JSON.stringify({
id: '2026-08-03-deep',
day: DAY,
mode: 'deep',
status: 'ok',
generatedAt: 3,
trigger: 'manual',
modelKey: '',
sections: { summary: 'legacy' },
totals: { sessionCount: 1, requestCount: 2, totalTokens: 3, costUsd: 4, errorCount: 0 },
}),
);

const store = createDailyReviewArchiveStore(root);
const archive = await store.getArchive('2026-08-03-deep');
assert.equal(archive?.range, 7);
assert.equal(archive?.sections.summary, 'legacy');
assert.deepEqual((await store.listArchives()).map((item) => item.range), [7]);
});

it('persists only the three canonical settings', async () => {
const root = await aRoot();
const store = createDailyReviewArchiveStore(root);
const next = await store.setConfig({
describe('Daily Review SQLite archive store', () => {
it('persists canonical config across store instances', async () => {
const root = await workspace();
const next = await createDailyReviewArchiveStore(root).setConfig({
enabled: true,
executeTime: '07:45',
modelKey: 'openai::gpt-5',
Expand All @@ -78,49 +30,63 @@ describe('Daily Review archive migration', () => {
executeTime: '07:45',
modelKey: 'openai::gpt-5',
});
assert.deepEqual(
JSON.parse(await readFile(join(root, 'daily-reviews', 'config.json'), 'utf8')),
next,
);
assert.deepEqual(await createDailyReviewArchiveStore(root).getConfig(), next);
});
});

describe('Daily Review archive corruption recovery', () => {
it('keeps healthy archives available when a sibling file is corrupt', async () => {
const root = await aRoot();
it('stores, replaces, and deletes one canonical archive', async () => {
const root = await workspace();
const store = createDailyReviewArchiveStore(root);
await store.putArchive(anArchive());
await writeFile(
join(root, 'daily-reviews', 'archive', '2026-08-02-1d.json'),
'{"id":"2026-08-02-1d"',
);
await store.putArchive(archive('2026-08-03-1d', 3));
await store.putArchive({
...archive('2026-08-03-1d', 4),
sections: { summary: 'updated' },
});

assert.equal((await store.getArchive('2026-08-03-1d'))?.sections.summary, 'updated');
assert.deepEqual((await store.listArchives()).map(({ id }) => id), ['2026-08-03-1d']);

assert.deepEqual((await store.listArchives()).map((archive) => archive.id), [
'2026-08-03-1d',
]);
await store.deleteArchive('2026-08-03-1d');
assert.equal(await store.getArchive('2026-08-03-1d'), null);
});

it('keeps healthy archives available when a sibling has an invalid schema', async () => {
const root = await aRoot();
it('orders newest first and prunes older archives in SQLite', async () => {
const root = await workspace();
const store = createDailyReviewArchiveStore(root);
await store.putArchive(anArchive());
await writeFile(
join(root, 'daily-reviews', 'archive', '2026-08-02-1d.json'),
JSON.stringify({ id: '2026-08-02-1d', range: 1 }),
);
await store.putArchive(archive('2026-08-01-1d', 1));
await store.putArchive(archive('2026-08-02-1d', 2));
await store.putArchive(archive('2026-08-03-1d', 3));

assert.deepEqual((await store.listArchives()).map((archive) => archive.id), [
'2026-08-03-1d',
]);
});

it('treats a corrupt canonical archive as missing so generation can replace it', async () => {
const root = await aRoot();
const archiveRoot = join(root, 'daily-reviews', 'archive');
await mkdir(archiveRoot, { recursive: true });
await writeFile(join(archiveRoot, '2026-08-03-1d.json'), '{');
await store.prune(2);

const store = createDailyReviewArchiveStore(root);
assert.equal(await store.getArchive('2026-08-03-1d'), null);
assert.deepEqual(
(await store.listArchives()).map(({ id }) => id),
['2026-08-03-1d', '2026-08-02-1d'],
);
});
});

async function workspace(): Promise<string> {
const root = await mkdtemp(join(tmpdir(), 'maka-daily-review-store-'));
roots.push(root);
return root;
}

function archive(id: string, generatedAt: number): DailyReviewArchive {
return {
id,
day: DAY,
range: 1,
status: 'ok',
generatedAt,
trigger: 'manual',
modelKey: '',
sections: { summary: 'healthy' },
totals: {
sessionCount: 1,
requestCount: 2,
totalTokens: 3,
costUsd: 4,
errorCount: 0,
},
};
}
Loading
Loading