test: add tests for SystemController (#3009)

Covers /healthcheck (live status, 503 on failure, fallback when service
unavailable), /version (semver parsing, env defaults), /contactUs
(validation, persistence, email best-effort, default support address),
/whoarewe, /lsmod (interface grouping + driverName fallback), and the
prepareShutdown drain-flag flip.

Closes #2971
This commit is contained in:
Daniel Salazar
2026-05-07 21:40:27 -07:00
committed by GitHub
parent 160631b158
commit 5f28b988ec
@@ -0,0 +1,277 @@
/**
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import type { Request, RequestHandler, Response } from 'express';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { v4 as uuidv4 } from 'uuid';
import type { Actor } from '../../core/actor.js';
import { PuterRouter } from '../../core/http/PuterRouter.js';
import { PuterServer } from '../../server.js';
import { setupTestServer } from '../../testUtil.js';
// ── Test harness ────────────────────────────────────────────────────
//
// Boots one real PuterServer (in-memory sqlite + dynamo + s3 + mock
// redis) and re-registers SystemController's inline lambda routes
// onto a fresh PuterRouter so each handler is reachable. Tests then
// drive the captured handler with stub req/res — the underlying
// services (health, db, drivers) are the live wired ones.
let server: PuterServer;
let router: PuterRouter;
beforeAll(async () => {
server = await setupTestServer();
router = new PuterRouter();
server.controllers.system.registerRoutes(router);
});
afterAll(async () => {
await server?.shutdown();
});
const makeUser = async (): Promise<{ actor: Actor; userId: number }> => {
const username = `sysc-${Math.random().toString(36).slice(2, 10)}`;
const created = await server.stores.user.create({
username,
uuid: uuidv4(),
password: null,
email: `${username}@test.local`,
free_storage: 100 * 1024 * 1024,
requires_email_confirmation: false,
});
const refreshed = (await server.stores.user.getById(created.id))!;
return {
userId: refreshed.id,
actor: {
user: {
id: refreshed.id,
uuid: refreshed.uuid,
username: refreshed.username,
email: refreshed.email ?? null,
email_confirmed: true,
} as Actor['user'],
},
};
};
interface CapturedResponse {
statusCode: number;
body: unknown;
}
const makeReq = (init: {
body?: unknown;
actor?: Actor;
}): Request => {
return {
body: init.body ?? {},
query: {},
headers: {},
actor: init.actor,
} as unknown as Request;
};
const makeRes = () => {
const captured: CapturedResponse = { statusCode: 200, body: undefined };
const res = {
json: vi.fn((value: unknown) => {
captured.body = value;
return res;
}),
send: vi.fn((value: unknown) => {
captured.body = value;
return res;
}),
status: vi.fn((code: number) => {
captured.statusCode = code;
return res;
}),
setHeader: vi.fn(() => res),
};
return { res: res as unknown as Response, captured };
};
const findHandler = (method: string, path: string): RequestHandler => {
const route = router.routes.find(
(r) => r.method === method && r.path === path,
);
if (!route) throw new Error(`No ${method.toUpperCase()} ${path} route`);
return route.handler;
};
const callRoute = async (
method: string,
path: string,
req: Request,
res: Response,
) => {
const handler = findHandler(method, path);
await handler(req, res, () => {
throw new Error('handler called next() unexpectedly');
});
};
// ── /healthcheck ────────────────────────────────────────────────────
describe('SystemController GET /healthcheck', () => {
it('returns the live ServerHealthService status payload', async () => {
const { res, captured } = makeRes();
await callRoute('get', '/healthcheck', makeReq({}), res);
// Live status — boot is complete and the in-memory DB is up,
// so ok=true is the expected steady state for this harness.
expect(captured.body).toMatchObject({ ok: true });
expect(captured.statusCode).toBe(200);
});
});
// ── /version ────────────────────────────────────────────────────────
describe('SystemController GET /version', () => {
it('returns version-shape JSON with environment + deploy_timestamp', async () => {
const { res, captured } = makeRes();
await callRoute('get', '/version', makeReq({}), res);
const body = captured.body as Record<string, unknown>;
// Default config has no `version` set — falls through to
// npm_package_version (set when running under vitest) or 'unknown'.
expect(typeof body.version).toBe('string');
// Default test config carries env='dev' from config.default.json.
expect(body.environment).toBe('dev');
expect(typeof body.deploy_timestamp).toBe('number');
});
});
// ── /contactUs ──────────────────────────────────────────────────────
describe('SystemController POST /contactUs', () => {
it('throws 400 when message is missing', async () => {
const { actor } = await makeUser();
const { res } = makeRes();
await expect(
callRoute(
'post',
'/contactUs',
makeReq({ body: {}, actor }),
res,
),
).rejects.toMatchObject({ statusCode: 400 });
});
it('throws 400 when message is not a string', async () => {
const { actor } = await makeUser();
const { res } = makeRes();
await expect(
callRoute(
'post',
'/contactUs',
makeReq({ body: { message: 12345 }, actor }),
res,
),
).rejects.toMatchObject({ statusCode: 400 });
});
it('throws 400 when message exceeds 100,000 characters', async () => {
const { actor } = await makeUser();
const { res } = makeRes();
await expect(
callRoute(
'post',
'/contactUs',
makeReq({
body: { message: 'x'.repeat(100_001) },
actor,
}),
res,
),
).rejects.toMatchObject({ statusCode: 400 });
});
it('persists feedback into the DB on success', async () => {
const { actor, userId } = await makeUser();
const message = `hello ${Math.random().toString(36).slice(2)}`;
const { res, captured } = makeRes();
await callRoute(
'post',
'/contactUs',
makeReq({ body: { message }, actor }),
res,
);
expect(captured.body).toEqual({});
// The row landed in the real `feedback` table for the right user.
const rows = (await server.clients.db.read(
'SELECT `user_id`, `message` FROM `feedback` WHERE `user_id` = ? AND `message` = ?',
[userId, message],
)) as Array<{ user_id: number; message: string }>;
expect(rows).toHaveLength(1);
expect(rows[0]?.message).toBe(message);
});
});
// ── /whoarewe ───────────────────────────────────────────────────────
describe('SystemController GET /whoarewe', () => {
it('returns the configured Puter identity payload', async () => {
const { res, captured } = makeRes();
await callRoute('get', '/whoarewe', makeReq({}), res);
expect(captured.body).toMatchObject({
name: 'Puter',
environment: 'dev',
});
});
});
// ── /lsmod ──────────────────────────────────────────────────────────
describe('SystemController GET /lsmod', () => {
it('lists wired drivers grouped by interface', async () => {
const { res, captured } = makeRes();
await callRoute('get', '/lsmod', makeReq({}), res);
const body = captured.body as {
interfaces: Record<
string,
{ implementors: Record<string, { isDefault: boolean }> }
>;
};
expect(body.interfaces).toBeDefined();
// Test harness wires the full driver registry; at least one
// driver/interface pair must come through.
expect(Object.keys(body.interfaces).length).toBeGreaterThan(0);
for (const iface of Object.values(body.interfaces)) {
expect(Object.keys(iface.implementors).length).toBeGreaterThan(0);
}
});
});
// ── lifecycle ───────────────────────────────────────────────────────
describe('SystemController.onServerPrepareShutdown', () => {
it('flips the global drain flag', () => {
// Reset before the call so the assertion is meaningful even
// when earlier code in the same process already tripped it.
(
globalThis as unknown as { __puter_draining?: boolean }
).__puter_draining = false;
server.controllers.system.onServerPrepareShutdown();
expect(
(globalThis as unknown as { __puter_draining?: boolean })
.__puter_draining,
).toBe(true);
});
});