Files
gilles 08919752e3 feat: socle BDD (tâche 1.9 Phase 1-2) + moteur APT (tâche 2 SJ-0→3) + WIP capabilities/auth/Rust
Checkpoint multi-chantiers (arbre vert : tsc 0 erreur, 70 tests, build OK).
- tâche 1.9 Phase 1 : schéma socle (machine_state/events/reports/raw_artifacts/
  hardware/metrics + colonnes étendues) + wiring refresh/execute. Migration 0002.
- tâche 1.9 Phase 2 : machine_credentials + machine_host_keys (non destructif,
  dual-read + backfill). Migration 0003. Fix séquence journal de migration.
- tâche 2 : SJ-0 (types étendus rétro-compatibles, réducteur Docker, resolveTemplate),
  SJ-1 (update-analyze enrichi), SJ-2 (apply + diff dpkg + timeout inactivité SSH),
  SJ-3 (reboot vérifié boot_id).
- WIP parallèle inclus : /api/capabilities, auth/apiTokens/apiClients, system metrics,
  scaffold app_rust, ajustements frontend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 19:50:25 +02:00

65 lines
2.1 KiB
TypeScript

// server/services/apiClients.test.ts
import { describe, expect, it, vi } from "vitest";
vi.mock("../db/client.js", () => ({
db: {},
schema: { apiClients: {} },
}));
vi.mock("../env.js", () => ({ env: { requireMasterKey: vi.fn() } }));
import { apiClientInternals } from "./apiClients.js";
describe("apiClientInternals", () => {
it("retombe sur read quand aucun scope n'est fourni", () => {
expect(apiClientInternals.normalizeScopes([])).toEqual(["read"]);
});
it("déduplique les scopes en gardant l'ordre", () => {
expect(apiClientInternals.normalizeScopes(["read", "operate", "read"])).toEqual([
"read",
"operate",
]);
});
it("rejette un scope inconnu", () => {
expect(() => apiClientInternals.normalizeScopes(["root" as never])).toThrow(
"Scope API inconnu: root",
);
});
it("convertit une ligne DB en vue sans token hash", () => {
const view = apiClientInternals.toView({
id: "client_1",
name: "App locale",
tokenPrefix: "su_abcdefghi",
tokenHash: "hash-secret",
scopesJson: '["read","operate"]',
createdAt: "2026-06-05T08:00:00.000Z",
lastUsedAt: null,
revokedAt: null,
});
expect(view).toEqual({
id: "client_1",
name: "App locale",
tokenPrefix: "su_abcdefghi",
scopes: ["read", "operate"],
createdAt: "2026-06-05T08:00:00.000Z",
lastUsedAt: null,
revokedAt: null,
});
expect(JSON.stringify(view)).not.toContain("hash-secret");
});
it("applique les scopes par capacité", () => {
expect(apiClientInternals.hasApiScope(["read"], "read")).toBe(true);
expect(apiClientInternals.hasApiScope(["read"], "operate")).toBe(false);
expect(apiClientInternals.hasApiScope(["operate"], "read")).toBe(true);
expect(apiClientInternals.hasApiScope(["operate"], "operate")).toBe(true);
expect(apiClientInternals.hasApiScope(["debug"], "debug")).toBe(true);
expect(apiClientInternals.hasApiScope(["admin"], "debug")).toBe(true);
expect(apiClientInternals.hasApiScope(["admin"], "admin")).toBe(true);
});
});