chore: 현재 작업 중간 커밋

This commit is contained in:
chungyeong
2026-03-05 11:00:45 +09:00
parent 02970df6af
commit be88b4fcec
43 changed files with 6837 additions and 466 deletions

View File

@@ -2,7 +2,38 @@
const test = require("node:test");
const assert = require("node:assert/strict");
const { InMemoryDashboardStore } = require("../src/dashboardStore");
const {
InMemoryDashboardStore,
MySqlDashboardStore,
createDashboardStore,
} = require("../src/dashboardStore");
async function withPatchedEnv(patch, fn) {
const backup = {};
for (const [key] of Object.entries(patch)) {
backup[key] = process.env[key];
}
for (const [key, value] of Object.entries(patch)) {
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
try {
return await fn();
} finally {
for (const [key, value] of Object.entries(backup)) {
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
}
}
test("in-memory store persists watch, poll result, events and controls", async () => {
const store = new InMemoryDashboardStore();
@@ -63,3 +94,130 @@ test("in-memory store persists watch, poll result, events and controls", async (
assert.equal(controlsAfter.crawlingEnabled, false);
assert.equal(controlsAfter.alertsEnabled, false);
});
test("mysql mode fails closed without fallback when configuration is missing", async () => {
await withPatchedEnv(
{
DASHBOARD_DB: undefined,
MYSQL_URL: undefined,
MYSQL_HOST: undefined,
MYSQL_USER: undefined,
MYSQL_DATABASE: undefined,
DASHBOARD_ALLOW_MEMORY_FALLBACK: undefined,
},
async () => {
await assert.rejects(
() =>
createDashboardStore({
mode: "mysql",
allowMemoryFallback: false,
}),
/MySQL 연결 정보가 필요합니다/
);
}
);
});
test("mysql mode can fallback to memory only when explicitly allowed", async () => {
await withPatchedEnv(
{
DASHBOARD_DB: undefined,
MYSQL_URL: undefined,
MYSQL_HOST: undefined,
MYSQL_USER: undefined,
MYSQL_DATABASE: undefined,
DASHBOARD_ALLOW_MEMORY_FALLBACK: undefined,
},
async () => {
const setup = await createDashboardStore({
mode: "mysql",
allowMemoryFallback: true,
});
assert.equal(setup.engine, "memory");
assert.match(setup.warning, /메모리 저장소/);
await setup.store.close();
}
);
});
test("mysql store init fails when required tables are missing", async () => {
const calls = [];
const store = new MySqlDashboardStore(
{
query: async (sql) => {
calls.push(sql);
return [[{ TABLE_NAME: "projects" }], []];
},
end: async () => {},
}
);
await assert.rejects(
() => store.init(),
/MySQL playground 스키마가 준비되지 않았습니다\..*project_documents.*project_events.*project_settings/
);
assert.equal(calls.length, 1);
});
test("mysql store init succeeds when all required tables exist", async () => {
const calls = [];
const store = new MySqlDashboardStore(
{
query: async (sql, params) => {
calls.push(sql);
if (String(sql).includes("FROM information_schema.TABLES")) {
return [
[
{ TABLE_NAME: "projects" },
{ TABLE_NAME: "project_documents" },
{ TABLE_NAME: "project_events" },
{ TABLE_NAME: "project_settings" },
],
[],
];
}
if (String(sql).includes("INSERT INTO projects")) {
assert.equal(params[0], "air-watcher");
return [{ affectedRows: 1 }, []];
}
throw new Error(`unexpected query: ${sql}`);
},
end: async () => {},
}
);
await assert.doesNotReject(() => store.init());
assert.equal(store.schemaMode, "playground");
assert.equal(calls.length, 2);
});
test("mysql store supports custom projectKey", async () => {
let insertedProjectKey = null;
const store = new MySqlDashboardStore({
query: async (sql, params) => {
if (String(sql).includes("FROM information_schema.TABLES")) {
return [
[
{ TABLE_NAME: "projects" },
{ TABLE_NAME: "project_documents" },
{ TABLE_NAME: "project_events" },
{ TABLE_NAME: "project_settings" },
],
[],
];
}
if (String(sql).includes("INSERT INTO projects")) {
insertedProjectKey = params[0];
return [{ affectedRows: 1 }, []];
}
throw new Error(`unexpected query: ${sql}`);
},
end: async () => {},
}, {
projectKey: "mini-app-a",
});
await store.init();
assert.equal(insertedProjectKey, "mini-app-a");
});