refactor(tests): move backend tests into src/backend/tests mirror tree

Backend *.test.ts files (and the test-support harness) no longer sit next
to source files; they live under src/backend/tests/ mirroring the source
layout. Imports rewritten accordingly; CLAUDE.md convention updated.
This commit is contained in:
LukeGus
2026-07-16 04:31:29 -05:00
parent 8b6037b7ff
commit 7f43932429
89 changed files with 200 additions and 160 deletions
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { AlertRepository } from "./alert-repository.js"; import { AlertRepository } from "../../../database/repositories/alert-repository.js";
describe("AlertRepository", () => { describe("AlertRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { ApiKeyRepository } from "./api-key-repository.js"; import { ApiKeyRepository } from "../../../database/repositories/api-key-repository.js";
describe("ApiKeyRepository", () => { describe("ApiKeyRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { AuditLogRepository } from "./audit-log-repository.js"; import { AuditLogRepository } from "../../../database/repositories/audit-log-repository.js";
describe("AuditLogRepository", () => { describe("AuditLogRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { C2sTunnelPresetRepository } from "./c2s-tunnel-preset-repository.js"; import { C2sTunnelPresetRepository } from "../../../database/repositories/c2s-tunnel-preset-repository.js";
describe("C2sTunnelPresetRepository", () => { describe("C2sTunnelPresetRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { CommandHistoryRepository } from "./command-history-repository.js"; import { CommandHistoryRepository } from "../../../database/repositories/command-history-repository.js";
describe("CommandHistoryRepository", () => { describe("CommandHistoryRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { DashboardServiceLinkRepository } from "./dashboard-service-link-repository.js"; import { DashboardServiceLinkRepository } from "../../../database/repositories/dashboard-service-link-repository.js";
describe("DashboardServiceLinkRepository", () => { describe("DashboardServiceLinkRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { DismissedAlertRepository } from "./dismissed-alert-repository.js"; import { DismissedAlertRepository } from "../../../database/repositories/dismissed-alert-repository.js";
describe("DismissedAlertRepository", () => { describe("DismissedAlertRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import crypto from "crypto"; import crypto from "crypto";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { FieldEncryptionBoundary } from "./field-encryption-boundary.js"; import { FieldEncryptionBoundary } from "../../../database/repositories/field-encryption-boundary.js";
describe("FieldEncryptionBoundary", () => { describe("FieldEncryptionBoundary", () => {
const userDataKey = crypto.randomBytes(32); const userDataKey = crypto.randomBytes(32);
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { FileManagerBookmarkRepository } from "./file-manager-bookmark-repository.js"; import { FileManagerBookmarkRepository } from "../../../database/repositories/file-manager-bookmark-repository.js";
describe("FileManagerBookmarkRepository", () => { describe("FileManagerBookmarkRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { HomepageItemRepository } from "./homepage-item-repository.js"; import { HomepageItemRepository } from "../../../database/repositories/homepage-item-repository.js";
describe("HomepageItemRepository", () => { describe("HomepageItemRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { HomepageLayoutRepository } from "./homepage-layout-repository.js"; import { HomepageLayoutRepository } from "../../../database/repositories/homepage-layout-repository.js";
describe("HomepageLayoutRepository", () => { describe("HomepageLayoutRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,8 +1,8 @@
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { CredentialRepository } from "./credential-repository.js"; import { CredentialRepository } from "../../../database/repositories/credential-repository.js";
import { HostRepository } from "./host-repository.js"; import { HostRepository } from "../../../database/repositories/host-repository.js";
import { DataCrypto } from "../../utils/data-crypto.js"; import { DataCrypto } from "../../../utils/data-crypto.js";
describe("HostRepository and CredentialRepository", () => { describe("HostRepository and CredentialRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { HostFolderRepository } from "./host-folder-repository.js"; import { HostFolderRepository } from "../../../database/repositories/host-folder-repository.js";
describe("HostFolderRepository", () => { describe("HostFolderRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { HostHealthRepository } from "./host-health-repository.js"; import { HostHealthRepository } from "../../../database/repositories/host-health-repository.js";
describe("HostHealthRepository", () => { describe("HostHealthRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { HostMetricsHistoryRepository } from "./host-metrics-history-repository.js"; import { HostMetricsHistoryRepository } from "../../../database/repositories/host-metrics-history-repository.js";
describe("HostMetricsHistoryRepository", () => { describe("HostMetricsHistoryRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { HostMetricsPreferenceRepository } from "./host-metrics-preference-repository.js"; import { HostMetricsPreferenceRepository } from "../../../database/repositories/host-metrics-preference-repository.js";
describe("HostMetricsPreferenceRepository", () => { describe("HostMetricsPreferenceRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,9 +1,9 @@
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { DataCrypto } from "../../utils/data-crypto.js"; import { DataCrypto } from "../../../utils/data-crypto.js";
import { HostResolutionRepository } from "./host-resolution-repository.js"; import { HostResolutionRepository } from "../../../database/repositories/host-resolution-repository.js";
vi.mock("../../utils/data-crypto.js", () => ({ vi.mock("../../../utils/data-crypto.js", () => ({
DataCrypto: { DataCrypto: {
getUserDataKey: vi.fn(), getUserDataKey: vi.fn(),
decryptRecord: vi.fn((_tableName, record) => record), decryptRecord: vi.fn((_tableName, record) => record),
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { NetworkTopologyRepository } from "./network-topology-repository.js"; import { NetworkTopologyRepository } from "../../../database/repositories/network-topology-repository.js";
describe("NetworkTopologyRepository", () => { describe("NetworkTopologyRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { OpenTabRepository } from "./open-tab-repository.js"; import { OpenTabRepository } from "../../../database/repositories/open-tab-repository.js";
describe("OpenTabRepository", () => { describe("OpenTabRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { OpksshTokenRepository } from "./opkssh-token-repository.js"; import { OpksshTokenRepository } from "../../../database/repositories/opkssh-token-repository.js";
describe("OpksshTokenRepository", () => { describe("OpksshTokenRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { RbacAccessRepository } from "./rbac-access-repository.js"; import { RbacAccessRepository } from "../../../database/repositories/rbac-access-repository.js";
describe("RbacAccessRepository", () => { describe("RbacAccessRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { RecentActivityRepository } from "./recent-activity-repository.js"; import { RecentActivityRepository } from "../../../database/repositories/recent-activity-repository.js";
describe("RecentActivityRepository", () => { describe("RecentActivityRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { RoleRepository } from "./role-repository.js"; import { RoleRepository } from "../../../database/repositories/role-repository.js";
describe("RoleRepository", () => { describe("RoleRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { SessionRecordingRepository } from "./session-recording-repository.js"; import { SessionRecordingRepository } from "../../../database/repositories/session-recording-repository.js";
describe("SessionRecordingRepository", () => { describe("SessionRecordingRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { SettingsRepository } from "./settings-repository.js"; import { SettingsRepository } from "../../../database/repositories/settings-repository.js";
describe("SettingsRepository", () => { describe("SettingsRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { SharedCredentialRepository } from "./shared-credential-repository.js"; import { SharedCredentialRepository } from "../../../database/repositories/shared-credential-repository.js";
describe("SharedCredentialRepository", () => { describe("SharedCredentialRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { SnippetRepository } from "./snippet-repository.js"; import { SnippetRepository } from "../../../database/repositories/snippet-repository.js";
describe("SnippetRepository", () => { describe("SnippetRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { SshCredentialUsageRepository } from "./ssh-credential-usage-repository.js"; import { SshCredentialUsageRepository } from "../../../database/repositories/ssh-credential-usage-repository.js";
describe("SshCredentialUsageRepository", () => { describe("SshCredentialUsageRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { SsoProviderRepository } from "./sso-provider-repository.js"; import { SsoProviderRepository } from "../../../database/repositories/sso-provider-repository.js";
describe("SsoProviderRepository", () => { describe("SsoProviderRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,7 +1,7 @@
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { DataCrypto } from "../../utils/data-crypto.js"; import { DataCrypto } from "../../../utils/data-crypto.js";
import { TermixIdentityCaRepository } from "./termix-identity-ca-repository.js"; import { TermixIdentityCaRepository } from "../../../database/repositories/termix-identity-ca-repository.js";
describe("TermixIdentityCaRepository", () => { describe("TermixIdentityCaRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { TermixIdentityRepository } from "./termix-identity-repository.js"; import { TermixIdentityRepository } from "../../../database/repositories/termix-identity-repository.js";
describe("TermixIdentityRepository", () => { describe("TermixIdentityRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,7 +1,7 @@
import Database from "better-sqlite3"; import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3"; import { drizzle } from "drizzle-orm/better-sqlite3";
import * as schema from "../db/schema.js"; import * as schema from "../../../database/db/schema.js";
import type { DatabaseContext } from "./database-context.js"; import type { DatabaseContext } from "../../../database/repositories/database-context.js";
export class TestSqliteDatabase { export class TestSqliteDatabase {
private sqlite: Database.Database | null = null; private sqlite: Database.Database | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { TmuxSessionTagRepository } from "./tmux-session-tag-repository.js"; import { TmuxSessionTagRepository } from "../../../database/repositories/tmux-session-tag-repository.js";
describe("TmuxSessionTagRepository", () => { describe("TmuxSessionTagRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { TransferRecentRepository } from "./transfer-recent-repository.js"; import { TransferRecentRepository } from "../../../database/repositories/transfer-recent-repository.js";
describe("TransferRecentRepository", () => { describe("TransferRecentRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { TrustedDeviceRepository } from "./trusted-device-repository.js"; import { TrustedDeviceRepository } from "../../../database/repositories/trusted-device-repository.js";
describe("TrustedDeviceRepository", () => { describe("TrustedDeviceRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { UserDataExportRepository } from "./user-data-export-repository.js"; import { UserDataExportRepository } from "../../../database/repositories/user-data-export-repository.js";
describe("UserDataExportRepository", () => { describe("UserDataExportRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { UserPreferenceRepository } from "./user-preference-repository.js"; import { UserPreferenceRepository } from "../../../database/repositories/user-preference-repository.js";
describe("UserPreferenceRepository", () => { describe("UserPreferenceRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,7 +1,7 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { SessionRepository } from "./session-repository.js"; import { SessionRepository } from "../../../database/repositories/session-repository.js";
import { UserRepository } from "./user-repository.js"; import { UserRepository } from "../../../database/repositories/user-repository.js";
describe("UserRepository and SessionRepository", () => { describe("UserRepository and SessionRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { VaultProfileRepository } from "./vault-profile-repository.js"; import { VaultProfileRepository } from "../../../database/repositories/vault-profile-repository.js";
describe("VaultProfileRepository", () => { describe("VaultProfileRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { TestSqliteDatabase } from "./test-support.js"; import { TestSqliteDatabase } from "./test-support.js";
import { VaultTokenRepository } from "./vault-token-repository.js"; import { VaultTokenRepository } from "../../../database/repositories/vault-token-repository.js";
describe("VaultTokenRepository", () => { describe("VaultTokenRepository", () => {
let adapter: TestSqliteDatabase | null = null; let adapter: TestSqliteDatabase | null = null;
@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest"; import { describe, it, expect } from "vitest";
import { parseSSHConfig } from "./host-bulk-routes.js"; import { parseSSHConfig } from "../../../database/routes/host-bulk-routes.js";
describe("parseSSHConfig", () => { describe("parseSSHConfig", () => {
it("parses a basic Host block", () => { it("parses a basic Host block", () => {
@@ -4,14 +4,14 @@ const mocks = vi.hoisted(() => ({
isUserDataUnlocked: vi.fn(), isUserDataUnlocked: vi.fn(),
})); }));
vi.mock("../../utils/simple-db-ops.js", () => ({ vi.mock("../../../utils/simple-db-ops.js", () => ({
SimpleDBOps: { SimpleDBOps: {
isUserDataUnlocked: mocks.isUserDataUnlocked, isUserDataUnlocked: mocks.isUserDataUnlocked,
}, },
})); }));
const { applyHostEnrollmentDefaults, requireHostEnrollmentAccessForPath } = const { applyHostEnrollmentDefaults, requireHostEnrollmentAccessForPath } =
await import("./host-enrollment-auth.js"); await import("../../../database/routes/host-enrollment-auth.js");
function response() { function response() {
const json = vi.fn(); const json = vi.fn();
@@ -6,7 +6,7 @@ import {
renameFolderPath, renameFolderPath,
stripSensitiveFields, stripSensitiveFields,
transformHostResponse, transformHostResponse,
} from "./host-normalizers.js"; } from "../../../database/routes/host-normalizers.js";
describe("isNonEmptyString", () => { describe("isNonEmptyString", () => {
it("accepts non-blank strings", () => { it("accepts non-blank strings", () => {
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
import { import {
isValidServiceLinkUrl, isValidServiceLinkUrl,
normalizeServiceLinkUrl, normalizeServiceLinkUrl,
} from "./service-link-url.js"; } from "../../../database/routes/service-link-url.js";
describe("service link URL handling", () => { describe("service link URL handling", () => {
it("keeps explicit http and https URLs", () => { it("keeps explicit http and https URLs", () => {
@@ -6,7 +6,7 @@ const mockSelect = vi.fn();
const mockDelete = vi.fn(); const mockDelete = vi.fn();
const mockInsert = vi.fn(); const mockInsert = vi.fn();
vi.mock("../db/index.js", () => ({ vi.mock("../../../database/db/index.js", () => ({
db: { db: {
select: mockSelect, select: mockSelect,
delete: mockDelete, delete: mockDelete,
@@ -14,11 +14,11 @@ vi.mock("../db/index.js", () => ({
}, },
})); }));
vi.mock("../../utils/logger.js", () => ({ vi.mock("../../../utils/logger.js", () => ({
apiLogger: { error: vi.fn(), warn: vi.fn(), info: vi.fn(), success: vi.fn() }, apiLogger: { error: vi.fn(), warn: vi.fn(), info: vi.fn(), success: vi.fn() },
})); }));
vi.mock("../../utils/auth-manager.js", () => ({ vi.mock("../../../utils/auth-manager.js", () => ({
AuthManager: { AuthManager: {
getInstance: () => ({ getInstance: () => ({
createAuthMiddleware: createAuthMiddleware:
@@ -92,7 +92,8 @@ describe("session-log-routes", () => {
mockStat.mockReturnValue({ size: 4096 }); mockStat.mockReturnValue({ size: 4096 });
// Directly call the route handler extracted from the module // Directly call the route handler extracted from the module
const { default: router } = await import("./session-log-routes.js"); const { default: router } =
await import("../../../database/routes/session-log-routes.js");
expect(router).toBeDefined(); expect(router).toBeDefined();
}); });
}); });
@@ -8,7 +8,7 @@ import {
generateCa, generateCa,
signUserCertificate, signUserCertificate,
ed25519RawFromLine, ed25519RawFromLine,
} from "./ssh-certificate.js"; } from "../../../database/routes/ssh-certificate.js";
function publicKeyObjectFromLine(line: string) { function publicKeyObjectFromLine(line: string) {
const raw = ed25519RawFromLine(line); const raw = ed25519RawFromLine(line);
@@ -4,7 +4,7 @@ import {
parsePublicKey, parsePublicKey,
matchesAlgoFilter, matchesAlgoFilter,
MAX_PUBLIC_KEY_LENGTH, MAX_PUBLIC_KEY_LENGTH,
} from "./termix-id-keys.js"; } from "../../../database/routes/termix-id-keys.js";
// Build a valid OpenSSH public-key line for a given type by encoding a wire // Build a valid OpenSSH public-key line for a given type by encoding a wire
// blob whose first string field equals the type (what parsePublicKey checks). // blob whose first string field equals the type (what parsePublicKey checks).
@@ -5,7 +5,7 @@ const mockUpdate = vi.fn();
const mockInsert = vi.fn(); const mockInsert = vi.fn();
const mockDelete = vi.fn(); const mockDelete = vi.fn();
vi.mock("../db/index.js", () => ({ vi.mock("../../../database/db/index.js", () => ({
db: { db: {
select: mockSelect, select: mockSelect,
update: mockUpdate, update: mockUpdate,
@@ -14,7 +14,7 @@ vi.mock("../db/index.js", () => ({
}, },
})); }));
vi.mock("../../utils/logger.js", () => ({ vi.mock("../../../utils/logger.js", () => ({
apiLogger: { error: vi.fn(), warn: vi.fn(), info: vi.fn(), success: vi.fn() }, apiLogger: { error: vi.fn(), warn: vi.fn(), info: vi.fn(), success: vi.fn() },
authLogger: { authLogger: {
error: vi.fn(), error: vi.fn(),
@@ -24,7 +24,7 @@ vi.mock("../../utils/logger.js", () => ({
}, },
})); }));
vi.mock("../../utils/auth-manager.js", () => ({ vi.mock("../../../utils/auth-manager.js", () => ({
AuthManager: { AuthManager: {
getInstance: () => ({ getInstance: () => ({
createAuthMiddleware: createAuthMiddleware:
@@ -40,16 +40,16 @@ vi.mock("../../utils/auth-manager.js", () => ({
}, },
})); }));
vi.mock("../../utils/audit-logger.js", () => ({ vi.mock("../../../utils/audit-logger.js", () => ({
logAudit: vi.fn(), logAudit: vi.fn(),
getRequestMeta: vi.fn(() => ({})), getRequestMeta: vi.fn(() => ({})),
})); }));
vi.mock("../../utils/data-crypto.js", () => ({ vi.mock("../../../utils/data-crypto.js", () => ({
DataCrypto: { getInstance: () => ({ encrypt: vi.fn(), decrypt: vi.fn() }) }, DataCrypto: { getInstance: () => ({ encrypt: vi.fn(), decrypt: vi.fn() }) },
})); }));
vi.mock("../repositories/factory.js", () => ({ vi.mock("../../../database/repositories/factory.js", () => ({
createCurrentTermixIdentityCaRepository: vi.fn(() => ({ createCurrentTermixIdentityCaRepository: vi.fn(() => ({
findPublicByIdentityId: vi.fn(), findPublicByIdentityId: vi.fn(),
findDecryptedByIdentityId: vi.fn(), findDecryptedByIdentityId: vi.fn(),
@@ -74,7 +74,7 @@ vi.mock("../repositories/factory.js", () => ({
})), })),
})); }));
vi.mock("./termix-id-keys.js", () => ({ vi.mock("../../../database/routes/termix-id-keys.js", () => ({
termixIdKeysRouter: { use: vi.fn() }, termixIdKeysRouter: { use: vi.fn() },
matchesAlgoFilter: vi.fn(() => true), matchesAlgoFilter: vi.fn(() => true),
})); }));
@@ -117,7 +117,8 @@ describe("GET /termix-id/linked-credentials", () => {
// First select (getIdentityForUser) returns nothing; second should not be called // First select (getIdentityForUser) returns nothing; second should not be called
mockSelect.mockReturnValueOnce(makeChain([])); mockSelect.mockReturnValueOnce(makeChain([]));
const { default: router } = await import("./termix-id.js"); const { default: router } =
await import("../../../database/routes/termix-id.js");
expect(router).toBeDefined(); expect(router).toBeDefined();
expect(router).toBeDefined(); expect(router).toBeDefined();
@@ -128,7 +129,8 @@ describe("GET /termix-id/linked-credentials", () => {
.mockReturnValueOnce(makeChain([IDENTITY_ROW])) // identity lookup .mockReturnValueOnce(makeChain([IDENTITY_ROW])) // identity lookup
.mockReturnValueOnce(makeChain([])); // keys lookup .mockReturnValueOnce(makeChain([])); // keys lookup
const { default: router } = await import("./termix-id.js"); const { default: router } =
await import("../../../database/routes/termix-id.js");
expect(router).toBeDefined(); expect(router).toBeDefined();
}); });
@@ -142,7 +144,8 @@ describe("GET /termix-id/linked-credentials", () => {
.mockReturnValueOnce(makeChain([IDENTITY_ROW])) .mockReturnValueOnce(makeChain([IDENTITY_ROW]))
.mockReturnValueOnce(makeChain(keys)); .mockReturnValueOnce(makeChain(keys));
const { default: router } = await import("./termix-id.js"); const { default: router } =
await import("../../../database/routes/termix-id.js");
expect(router).toBeDefined(); expect(router).toBeDefined();
}); });
@@ -152,7 +155,8 @@ describe("GET /termix-id/linked-credentials", () => {
.mockReturnValueOnce(makeChain([IDENTITY_ROW])) .mockReturnValueOnce(makeChain([IDENTITY_ROW]))
.mockReturnValueOnce(makeChain(keys)); .mockReturnValueOnce(makeChain(keys));
const { default: router } = await import("./termix-id.js"); const { default: router } =
await import("../../../database/routes/termix-id.js");
expect(router).toBeDefined(); expect(router).toBeDefined();
}); });
}); });
@@ -1,7 +1,7 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
// user-oidc-utils imports the logger; stub it so importing stays side-effect-free. // user-oidc-utils imports the logger; stub it so importing stays side-effect-free.
vi.mock("../../utils/logger.js", () => ({ vi.mock("../../../utils/logger.js", () => ({
authLogger: { authLogger: {
debug: vi.fn(), debug: vi.fn(),
info: vi.fn(), info: vi.fn(),
@@ -16,7 +16,7 @@ const {
getOIDCConfigFromEnv, getOIDCConfigFromEnv,
extractOidcGroups, extractOidcGroups,
validateLogoutTokenClaims, validateLogoutTokenClaims,
} = await import("./user-oidc-utils.js"); } = await import("../../../database/routes/user-oidc-utils.js");
const BACKCHANNEL_LOGOUT_EVENT = const BACKCHANNEL_LOGOUT_EVENT =
"http://schemas.openid.net/event/backchannel-logout"; "http://schemas.openid.net/event/backchannel-logout";
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest";
import type { AuthManager } from "../../utils/auth-manager.js"; import type { AuthManager } from "../../../utils/auth-manager.js";
const calls = vi.hoisted(() => ({ const calls = vi.hoisted(() => ({
userUpdates: [] as Array<[string, Record<string, unknown>]>, userUpdates: [] as Array<[string, Record<string, unknown>]>,
@@ -17,7 +17,7 @@ function deletingRepo(label: string) {
}); });
} }
vi.mock("../repositories/factory.js", () => ({ vi.mock("../../../database/repositories/factory.js", () => ({
createCurrentUserRepository: () => ({ createCurrentUserRepository: () => ({
update: async (userId: string, update: Record<string, unknown>) => { update: async (userId: string, update: Record<string, unknown>) => {
calls.userUpdates.push([userId, update]); calls.userUpdates.push([userId, update]);
@@ -34,7 +34,7 @@ vi.mock("../repositories/factory.js", () => ({
createCurrentCredentialRepository: deletingRepo("credentials"), createCurrentCredentialRepository: deletingRepo("credentials"),
})); }));
vi.mock("../../utils/user-keys.js", () => ({ vi.mock("../../../utils/user-keys.js", () => ({
UserKeyManager: { UserKeyManager: {
getInstance: () => ({ getInstance: () => ({
rotateUserDEK: async (userId: string) => { rotateUserDEK: async (userId: string) => {
@@ -45,13 +45,13 @@ vi.mock("../../utils/user-keys.js", () => ({
}, },
})); }));
vi.mock("../../utils/crypto-migration/dek-migration.js", () => ({ vi.mock("../../../utils/crypto-migration/dek-migration.js", () => ({
deleteLegacyWraps: async (userId: string) => { deleteLegacyWraps: async (userId: string) => {
calls.legacyWrapsDeletedFor.push(userId); calls.legacyWrapsDeletedFor.push(userId);
}, },
})); }));
import { resetUserPassword } from "./user-password-reset-routes.js"; import { resetUserPassword } from "../../../database/routes/user-password-reset-routes.js";
function fakeAuthManager(unlocked: boolean): AuthManager { function fakeAuthManager(unlocked: boolean): AuthManager {
return { return {
@@ -6,13 +6,13 @@ import speakeasy from "speakeasy";
// importing stays inert. // importing stays inert.
const userRepositoryUpdate = vi.fn().mockResolvedValue(null); const userRepositoryUpdate = vi.fn().mockResolvedValue(null);
vi.mock("../repositories/factory.js", () => ({ vi.mock("../../../database/repositories/factory.js", () => ({
createCurrentUserRepository: () => ({ createCurrentUserRepository: () => ({
update: userRepositoryUpdate, update: userRepositoryUpdate,
}), }),
})); }));
vi.mock("../../utils/logger.js", () => ({ vi.mock("../../../utils/logger.js", () => ({
authLogger: { authLogger: {
debug: vi.fn(), debug: vi.fn(),
info: vi.fn(), info: vi.fn(),
@@ -22,7 +22,8 @@ vi.mock("../../utils/logger.js", () => ({
}, },
})); }));
const { verifyTotpReauth } = await import("./user-totp-routes.js"); const { verifyTotpReauth } =
await import("../../../database/routes/user-totp-routes.js");
type AnyUser = Parameters<typeof verifyTotpReauth>[0]; type AnyUser = Parameters<typeof verifyTotpReauth>[0];
@@ -3,7 +3,7 @@ import {
pickResolvedUsername, pickResolvedUsername,
pickResolvedPassword, pickResolvedPassword,
expandOidcUsername, expandOidcUsername,
} from "./credential-username.js"; } from "../../hosts/credential-username.js";
describe("pickResolvedUsername", () => { describe("pickResolvedUsername", () => {
it("keeps the host username when one is set, even with a credential username", () => { it("keeps the host username when one is set, even with a credential username", () => {
@@ -63,40 +63,40 @@ describe("expandOidcUsername", () => {
}); });
it("expands the placeholder with the user's OIDC identifier", async () => { it("expands the placeholder with the user's OIDC identifier", async () => {
vi.doMock("../database/repositories/factory.js", () => ({ vi.doMock("../../database/repositories/factory.js", () => ({
createCurrentUserRepository: () => ({ createCurrentUserRepository: () => ({
findById: async () => ({ oidcIdentifier: "jdoe" }), findById: async () => ({ oidcIdentifier: "jdoe" }),
}), }),
})); }));
const { expandOidcUsername: expand } = const { expandOidcUsername: expand } =
await import("./credential-username.js"); await import("../../hosts/credential-username.js");
expect(await expand("$oidc.preferred_username", "user-1")).toBe("jdoe"); expect(await expand("$oidc.preferred_username", "user-1")).toBe("jdoe");
}); });
it("leaves the placeholder as-is when the user has no OIDC identifier", async () => { it("leaves the placeholder as-is when the user has no OIDC identifier", async () => {
vi.doMock("../database/repositories/factory.js", () => ({ vi.doMock("../../database/repositories/factory.js", () => ({
createCurrentUserRepository: () => ({ createCurrentUserRepository: () => ({
findById: async () => ({ oidcIdentifier: null }), findById: async () => ({ oidcIdentifier: null }),
}), }),
})); }));
const { expandOidcUsername: expand } = const { expandOidcUsername: expand } =
await import("./credential-username.js"); await import("../../hosts/credential-username.js");
expect(await expand("$oidc.preferred_username", "user-1")).toBe( expect(await expand("$oidc.preferred_username", "user-1")).toBe(
"$oidc.preferred_username", "$oidc.preferred_username",
); );
}); });
it("returns the username unchanged when the DB lookup throws", async () => { it("returns the username unchanged when the DB lookup throws", async () => {
vi.doMock("../database/repositories/factory.js", () => ({ vi.doMock("../../database/repositories/factory.js", () => ({
createCurrentUserRepository: () => { createCurrentUserRepository: () => {
throw new Error("DB unavailable"); throw new Error("DB unavailable");
}, },
})); }));
const { expandOidcUsername: expand } = const { expandOidcUsername: expand } =
await import("./credential-username.js"); await import("../../hosts/credential-username.js");
expect(await expand("$oidc.preferred_username", "user-1")).toBe( expect(await expand("$oidc.preferred_username", "user-1")).toBe(
"$oidc.preferred_username", "$oidc.preferred_username",
); );
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { buildDeleteCommand } from "./file-manager-operation-commands.js"; import { buildDeleteCommand } from "../../hosts/file-manager-operation-commands.js";
describe("buildDeleteCommand", () => { describe("buildDeleteCommand", () => {
it("builds a PowerShell 5.1 compatible delete command for Windows files", () => { it("builds a PowerShell 5.1 compatible delete command for Windows files", () => {
@@ -6,7 +6,7 @@ import {
getMimeType, getMimeType,
detectBinary, detectBinary,
parseLsDateToTimestamp, parseLsDateToTimestamp,
} from "./file-manager-utils.js"; } from "../../hosts/file-manager-utils.js";
describe("isExecutableFile", () => { describe("isExecutableFile", () => {
it("flags scripts with execute permission", () => { it("flags scripts with execute permission", () => {
@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
vi.mock("../../utils/logger.js", () => ({ vi.mock("../../../utils/logger.js", () => ({
guacLogger: { guacLogger: {
debug: vi.fn(), debug: vi.fn(),
info: vi.fn(), info: vi.fn(),
@@ -10,7 +10,8 @@ vi.mock("../../utils/logger.js", () => ({
}, },
})); }));
const { GuacamoleTokenService } = await import("./token-service.js"); const { GuacamoleTokenService } =
await import("../../../hosts/guacamole/token-service.js");
describe("GuacamoleTokenService", () => { describe("GuacamoleTokenService", () => {
const tokenService = GuacamoleTokenService.getInstance(); const tokenService = GuacamoleTokenService.getInstance();
@@ -4,8 +4,8 @@ import {
supportsMetrics, supportsMetrics,
isTcpPingEnabled, isTcpPingEnabled,
tcpPingThroughJumpHost, tcpPingThroughJumpHost,
} from "./host-metrics-helpers.js"; } from "../../hosts/host-metrics-helpers.js";
import { createConnectionLog } from "./connection-log.js"; import { createConnectionLog } from "../../hosts/connection-log.js";
describe("supportsMetrics", () => { describe("supportsMetrics", () => {
it("supports plain ssh hosts", () => { it("supports plain ssh hosts", () => {
@@ -5,7 +5,7 @@ import {
isMetricCardId, isMetricCardId,
isManagerCardId, isManagerCardId,
METRIC_CARD_IDS, METRIC_CARD_IDS,
} from "../../types/host-metrics.js"; } from "../../../types/host-metrics.js";
describe("deriveEnabledWidgets", () => { describe("deriveEnabledWidgets", () => {
it("returns only metric ids, in canonical order, deduped", () => { it("returns only metric ids, in canonical order, deduped", () => {
@@ -1,5 +1,8 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { ConcurrentLimiter, HostPollCache } from "./host-metrics-state.js"; import {
ConcurrentLimiter,
HostPollCache,
} from "../../hosts/host-metrics-state.js";
describe("ConcurrentLimiter", () => { describe("ConcurrentLimiter", () => {
it("never exceeds max concurrent runners", async () => { it("never exceeds max concurrent runners", async () => {
@@ -1,11 +1,14 @@
import { describe, it, expect, vi, beforeEach } from "vitest"; import { describe, it, expect, vi, beforeEach } from "vitest";
const execCommand = vi.fn(); const execCommand = vi.fn();
vi.mock("../widgets/common-utils.js", () => ({ vi.mock("../../../hosts/widgets/common-utils.js", () => ({
execCommand: (...args: unknown[]) => execCommand(...args), execCommand: (...args: unknown[]) => execCommand(...args),
})); }));
import { execElevated, ElevationError } from "./exec-elevated.js"; import {
execElevated,
ElevationError,
} from "../../../hosts/managers/exec-elevated.js";
import type { Client } from "ssh2"; import type { Client } from "ssh2";
const fakeClient = {} as Client; const fakeClient = {} as Client;
@@ -1,6 +1,9 @@
import { describe, it, expect } from "vitest"; import { describe, it, expect } from "vitest";
import { buildSudoCommand, shellSingleQuote } from "./exec-elevated.js"; import {
import { parsePlatformProbe } from "./platform.js"; buildSudoCommand,
shellSingleQuote,
} from "../../../hosts/managers/exec-elevated.js";
import { parsePlatformProbe } from "../../../hosts/managers/platform.js";
import { import {
isValidSystemdUnit, isValidSystemdUnit,
isValidPid, isValidPid,
@@ -12,32 +15,47 @@ import {
isValidSignal, isValidSignal,
isValidServiceAction, isValidServiceAction,
isAllowedPath, isAllowedPath,
} from "./validation.js"; } from "../../../hosts/managers/validation.js";
import { parseServiceList, buildServiceActionCommand } from "./services.js"; import {
import { parseProcessList, buildKillCommand } from "./processes.js"; parseServiceList,
import { parseDfMounts, parseTopMemory } from "./simple-reads.js"; buildServiceActionCommand,
} from "../../../hosts/managers/services.js";
import {
parseProcessList,
buildKillCommand,
} from "../../../hosts/managers/processes.js";
import {
parseDfMounts,
parseTopMemory,
} from "../../../hosts/managers/simple-reads.js";
import { import {
parseCrontab, parseCrontab,
serializeCrontab, serializeCrontab,
isValidCronSchedule, isValidCronSchedule,
buildApplyCrontabCommand, buildApplyCrontabCommand,
} from "./cron.js"; } from "../../../hosts/managers/cron.js";
import { import {
buildPackageActionCommand, buildPackageActionCommand,
parseUpgradable, parseUpgradable,
buildListUpgradableCommand, buildListUpgradableCommand,
} from "./packages.js"; } from "../../../hosts/managers/packages.js";
import { import {
buildIssueCommand, buildIssueCommand,
buildRenewCommand, buildRenewCommand,
buildRevokeCommand, buildRevokeCommand,
isValidCertName, isValidCertName,
parseCertbotCertificates, parseCertbotCertificates,
} from "./ssl.js"; } from "../../../hosts/managers/ssl.js";
import { buildIptablesRuleCommand, buildNftRuleCommand } from "./firewall.js"; import {
import { parsePasswd, parseSudoers } from "./users.js"; buildIptablesRuleCommand,
import { buildHealthCheckCommand, parseHealthResult } from "./health.js"; buildNftRuleCommand,
import { buildTailCommand, clampLines } from "./logs.js"; } from "../../../hosts/managers/firewall.js";
import { parsePasswd, parseSudoers } from "../../../hosts/managers/users.js";
import {
buildHealthCheckCommand,
parseHealthResult,
} from "../../../hosts/managers/health.js";
import { buildTailCommand, clampLines } from "../../../hosts/managers/logs.js";
describe("exec-elevated", () => { describe("exec-elevated", () => {
it("single-quotes and escapes for the shell", () => { it("single-quotes and escapes for the shell", () => {
@@ -4,7 +4,7 @@ import {
resolveHostForSshConnect, resolveHostForSshConnect,
resolveSshConnectConfigHost, resolveSshConnectConfigHost,
shouldResolveBeforeSshConnect, shouldResolveBeforeSshConnect,
} from "./ssh-dns.js"; } from "../../hosts/ssh-dns.js";
describe("SSH DNS resolution", () => { describe("SSH DNS resolution", () => {
it("retries transient EAI_AGAIN errors before returning an address", async () => { it("retries transient EAI_AGAIN errors before returning an address", async () => {
@@ -6,7 +6,7 @@ vi.mock("fs/promises", () => ({
access: mockAccess, access: mockAccess,
})); }));
import { resolveAgentSocket } from "./terminal-auth-helpers.js"; import { resolveAgentSocket } from "../../hosts/terminal-auth-helpers.js";
describe("resolveAgentSocket", () => { describe("resolveAgentSocket", () => {
const originalEnv = process.env.SSH_AUTH_SOCK; const originalEnv = process.env.SSH_AUTH_SOCK;
@@ -1,6 +1,6 @@
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { performPortKnocking } from "./terminal-auth-helpers.js"; import { performPortKnocking } from "../../hosts/terminal-auth-helpers.js";
class FakeTcpSocket extends EventEmitter { class FakeTcpSocket extends EventEmitter {
readonly connect = vi.fn(); readonly connect = vi.fn();
@@ -4,18 +4,18 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
const mockCreate = vi.fn().mockResolvedValue({ id: 1 }); const mockCreate = vi.fn().mockResolvedValue({ id: 1 });
const mockUpdateEnded = vi.fn().mockResolvedValue(undefined); const mockUpdateEnded = vi.fn().mockResolvedValue(undefined);
vi.mock("../database/db/index.js", () => ({ vi.mock("../../database/db/index.js", () => ({
getDb: () => ({}), getDb: () => ({}),
})); }));
vi.mock("../database/repositories/factory.js", () => ({ vi.mock("../../database/repositories/factory.js", () => ({
createCurrentSessionRecordingRepository: () => ({ createCurrentSessionRecordingRepository: () => ({
create: mockCreate, create: mockCreate,
updateEnded: mockUpdateEnded, updateEnded: mockUpdateEnded,
}), }),
})); }));
vi.mock("../utils/logger.js", () => ({ vi.mock("../../utils/logger.js", () => ({
sshLogger: { sshLogger: {
info: vi.fn(), info: vi.fn(),
warn: vi.fn(), warn: vi.fn(),
@@ -49,7 +49,8 @@ vi.mock("fs", () => ({
}, },
})); }));
const { sessionManager } = await import("./terminal-session-manager.js"); const { sessionManager } =
await import("../../hosts/terminal-session-manager.js");
describe("TerminalSessionManager - session logging", () => { describe("TerminalSessionManager - session logging", () => {
beforeEach(() => { beforeEach(() => {
@@ -1,7 +1,11 @@
import { EventEmitter } from "node:events"; import { EventEmitter } from "node:events";
import type { Client } from "ssh2"; import type { Client } from "ssh2";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { detectTmux, tmuxCommand, withTmuxPath } from "./tmux-helper.js"; import {
detectTmux,
tmuxCommand,
withTmuxPath,
} from "../../hosts/tmux-helper.js";
describe("tmux command path handling", () => { describe("tmux command path handling", () => {
it("adds common non-login shell tmux paths", () => { it("adds common non-login shell tmux paths", () => {
@@ -9,7 +9,7 @@ import {
buildPaneMetrics, buildPaneMetrics,
attachPanesToWindows, attachPanesToWindows,
shellEscape, shellEscape,
} from "./tmux-monitor-helpers.js"; } from "../../hosts/tmux-monitor-helpers.js";
function join(...fields: (string | number)[]): string { function join(...fields: (string | number)[]): string {
return fields.join(SEP); return fields.join(SEP);
@@ -11,7 +11,7 @@ import {
completeVaultOidc, completeVaultOidc,
signWithVault, signWithVault,
type VaultProfileConfig, type VaultProfileConfig,
} from "./vault-signer-core.js"; } from "../../hosts/vault-signer-core.js";
const { utils: ssh2Utils } = ssh2Pkg; const { utils: ssh2Utils } = ssh2Pkg;
@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest"; import { describe, it, expect } from "vitest";
import { toFixedNum, kibToGiB } from "./common-utils.js"; import { toFixedNum, kibToGiB } from "../../../hosts/widgets/common-utils.js";
describe("toFixedNum", () => { describe("toFixedNum", () => {
it("rounds to the requested digit count", () => { it("rounds to the requested digit count", () => {
@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest"; import { describe, it, expect } from "vitest";
import { parseCpuLine } from "./cpu-collector.js"; import { parseCpuLine } from "../../../hosts/widgets/cpu-collector.js";
describe("parseCpuLine", () => { describe("parseCpuLine", () => {
it("parses a standard /proc/stat cpu line", () => { it("parses a standard /proc/stat cpu line", () => {
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
import { import {
parseSensorsOutput, parseSensorsOutput,
parseSysfsThermalOutput, parseSysfsThermalOutput,
} from "./temperature-collector.js"; } from "../../../hosts/widgets/temperature-collector.js";
describe("temperature collectors", () => { describe("temperature collectors", () => {
it("parses sysfs thermal zone output", () => { it("parses sysfs thermal zone output", () => {
@@ -2,13 +2,13 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
const createMock = vi.fn().mockResolvedValue(undefined); const createMock = vi.fn().mockResolvedValue(undefined);
vi.mock("../database/repositories/factory.js", () => ({ vi.mock("../../database/repositories/factory.js", () => ({
createCurrentAuditLogRepository: vi.fn(() => ({ createCurrentAuditLogRepository: vi.fn(() => ({
create: createMock, create: createMock,
})), })),
})); }));
import { logAudit, getRequestMeta } from "./audit-logger.js"; import { logAudit, getRequestMeta } from "../../utils/audit-logger.js";
describe("logAudit", () => { describe("logAudit", () => {
beforeEach(() => { beforeEach(() => {
@@ -11,7 +11,7 @@ const mocks = vi.hoisted(() => ({
saveDatabase: vi.fn().mockResolvedValue(undefined), saveDatabase: vi.fn().mockResolvedValue(undefined),
})); }));
vi.mock("../database/db/index.js", async () => { vi.mock("../../database/db/index.js", async () => {
const { default: Database } = await import("better-sqlite3"); const { default: Database } = await import("better-sqlite3");
const { drizzle } = await import("drizzle-orm/better-sqlite3"); const { drizzle } = await import("drizzle-orm/better-sqlite3");
const sqlite = new Database(":memory:"); const sqlite = new Database(":memory:");
@@ -24,7 +24,7 @@ vi.mock("../database/db/index.js", async () => {
}; };
}); });
vi.mock("./user-keys.js", () => ({ vi.mock("../../utils/user-keys.js", () => ({
UserKeyManager: { UserKeyManager: {
getInstance: () => ({ getInstance: () => ({
invalidate: vi.fn(), invalidate: vi.fn(),
@@ -34,15 +34,15 @@ vi.mock("./user-keys.js", () => ({
}, },
})); }));
vi.mock("./system-crypto.js", () => ({ vi.mock("../../utils/system-crypto.js", () => ({
SystemCrypto: { getInstance: () => ({}) }, SystemCrypto: { getInstance: () => ({}) },
})); }));
vi.mock("./data-crypto.js", () => ({ vi.mock("../../utils/data-crypto.js", () => ({
DataCrypto: { getInstance: () => ({}) }, DataCrypto: { getInstance: () => ({}) },
})); }));
vi.mock("./logger.js", () => ({ vi.mock("../../utils/logger.js", () => ({
authLogger: { authLogger: {
debug: vi.fn(), debug: vi.fn(),
info: vi.fn(), info: vi.fn(),
@@ -59,7 +59,7 @@ vi.mock("./logger.js", () => ({
}, },
})); }));
const { AuthManager } = await import("./auth-manager.js"); const { AuthManager } = await import("../../utils/auth-manager.js");
const authManager = AuthManager.getInstance(); const authManager = AuthManager.getInstance();
type SessionInput = { type SessionInput = {
@@ -10,13 +10,13 @@ const mocks = vi.hoisted(() => ({
adoptRecoveredDEK: vi.fn(async () => {}), adoptRecoveredDEK: vi.fn(async () => {}),
})); }));
vi.mock("../database/db/index.js", () => ({ vi.mock("../../database/db/index.js", () => ({
db: {}, db: {},
getDb: () => ({}), getDb: () => ({}),
saveMemoryDatabaseToFile: vi.fn(), saveMemoryDatabaseToFile: vi.fn(),
})); }));
vi.mock("../database/repositories/factory.js", () => ({ vi.mock("../../database/repositories/factory.js", () => ({
createCurrentSettingsRepository: () => ({ get: async () => null }), createCurrentSettingsRepository: () => ({ get: async () => null }),
createCurrentSessionRepository: () => ({}), createCurrentSessionRepository: () => ({}),
createCurrentUserRepository: () => ({}), createCurrentUserRepository: () => ({}),
@@ -25,7 +25,7 @@ vi.mock("../database/repositories/factory.js", () => ({
getCurrentSettingValue: () => null, getCurrentSettingValue: () => null,
})); }));
vi.mock("./user-keys.js", () => ({ vi.mock("../../utils/user-keys.js", () => ({
UserKeyManager: { UserKeyManager: {
getInstance: () => ({ getInstance: () => ({
hasUserDEK: mocks.hasUserDEK, hasUserDEK: mocks.hasUserDEK,
@@ -35,12 +35,12 @@ vi.mock("./user-keys.js", () => ({
}, },
})); }));
vi.mock("./crypto-migration/dek-migration.js", () => ({ vi.mock("../../utils/crypto-migration/dek-migration.js", () => ({
adoptRecoveredDEK: mocks.adoptRecoveredDEK, adoptRecoveredDEK: mocks.adoptRecoveredDEK,
migratePasswordUserAtLogin: vi.fn(async () => true), migratePasswordUserAtLogin: vi.fn(async () => true),
})); }));
vi.mock("./system-crypto.js", () => ({ vi.mock("../../utils/system-crypto.js", () => ({
SystemCrypto: { SystemCrypto: {
getInstance: () => ({ getInstance: () => ({
getJWTSecret: async () => jwtSecret, getJWTSecret: async () => jwtSecret,
@@ -49,7 +49,7 @@ vi.mock("./system-crypto.js", () => ({
}, },
})); }));
const { AuthManager } = await import("./auth-manager.js"); const { AuthManager } = await import("../../utils/auth-manager.js");
const authManager = AuthManager.getInstance(); const authManager = AuthManager.getInstance();
function makeLegacyDataKeyWrap( function makeLegacyDataKeyWrap(
@@ -4,7 +4,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
const settingsStore = new Map<string, string>(); const settingsStore = new Map<string, string>();
let userRows: Array<{ id: string }> = []; let userRows: Array<{ id: string }> = [];
vi.mock("../../database/repositories/factory.js", () => ({ vi.mock("../../../database/repositories/factory.js", () => ({
getCurrentSettingValue: (key: string) => settingsStore.get(key) ?? null, getCurrentSettingValue: (key: string) => settingsStore.get(key) ?? null,
createCurrentSettingsRepository: () => ({ createCurrentSettingsRepository: () => ({
upsert: async (key: string, value: string) => { upsert: async (key: string, value: string) => {
@@ -24,7 +24,7 @@ vi.mock("../../database/repositories/factory.js", () => ({
const masterKey = crypto.randomBytes(32); const masterKey = crypto.randomBytes(32);
vi.mock("../system-crypto.js", () => ({ vi.mock("../../../utils/system-crypto.js", () => ({
SystemCrypto: { SystemCrypto: {
getInstance: () => ({ getInstance: () => ({
getEncryptionKey: async () => masterKey, getEncryptionKey: async () => masterKey,
@@ -32,13 +32,13 @@ vi.mock("../system-crypto.js", () => ({
}, },
})); }));
import { UserKeyManager } from "../user-keys.js"; import { UserKeyManager } from "../../../utils/user-keys.js";
import { import {
adoptRecoveredDEK, adoptRecoveredDEK,
legacySettingsKeys, legacySettingsKeys,
migratePasswordUserAtLogin, migratePasswordUserAtLogin,
runBootDekMigration, runBootDekMigration,
} from "./dek-migration.js"; } from "../../../utils/crypto-migration/dek-migration.js";
const manager = UserKeyManager.getInstance(); const manager = UserKeyManager.getInstance();
@@ -7,7 +7,7 @@ import {
DATABASE_LAYER_PREUPGRADE_BACKUP_PREFIX, DATABASE_LAYER_PREUPGRADE_BACKUP_PREFIX,
DATABASE_LAYER_SKIP_PREUPGRADE_BACKUP_ENV, DATABASE_LAYER_SKIP_PREUPGRADE_BACKUP_ENV,
ensureDatabaseLayerPreupgradeBackup, ensureDatabaseLayerPreupgradeBackup,
} from "./database-layer-preupgrade-backup.js"; } from "../../utils/database-layer-preupgrade-backup.js";
const tempDirs: string[] = []; const tempDirs: string[] = [];
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import { DatabaseSaveTrigger } from "./database-save-trigger.js"; import { DatabaseSaveTrigger } from "../../utils/database-save-trigger.js";
describe("DatabaseSaveTrigger", () => { describe("DatabaseSaveTrigger", () => {
afterEach(() => { afterEach(() => {
@@ -1,6 +1,6 @@
import { describe, it, expect } from "vitest"; import { describe, it, expect } from "vitest";
import crypto from "crypto"; import crypto from "crypto";
import { FieldCrypto } from "./field-crypto.js"; import { FieldCrypto } from "../../utils/field-crypto.js";
const masterKey = crypto.randomBytes(32); const masterKey = crypto.randomBytes(32);
@@ -3,7 +3,7 @@ import fs from "fs";
import os from "os"; import os from "os";
import path from "path"; import path from "path";
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { LegacySqliteDatabaseCopyStore } from "./legacy-sqlite-database-copy-store.js"; import { LegacySqliteDatabaseCopyStore } from "../../utils/legacy-sqlite-database-copy-store.js";
describe("LegacySqliteDatabaseCopyStore", () => { describe("LegacySqliteDatabaseCopyStore", () => {
let tempDir: string | null = null; let tempDir: string | null = null;
@@ -1,5 +1,5 @@
import { describe, it, expect, beforeEach } from "vitest"; import { describe, it, expect, beforeEach } from "vitest";
import { loginRateLimiter } from "./login-rate-limiter.js"; import { loginRateLimiter } from "../../utils/login-rate-limiter.js";
// The limiter is a shared singleton, so each test uses unique ip/username keys // The limiter is a shared singleton, so each test uses unique ip/username keys
// and resets them to stay isolated. // and resets them to stay isolated.
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
import { import {
getDesktopOidcCallbackUrl, getDesktopOidcCallbackUrl,
isOidcTokenCallback, isOidcTokenCallback,
} from "./oidc-desktop-callback"; } from "../../utils/oidc-desktop-callback.js";
describe("getDesktopOidcCallbackUrl", () => { describe("getDesktopOidcCallbackUrl", () => {
it("uses localhost so browsers do not upgrade the loopback callback", () => { it("uses localhost so browsers do not upgrade the loopback callback", () => {
@@ -4,8 +4,8 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
// top level. Stub both so importing the module does not spin up the real // top level. Stub both so importing the module does not spin up the real
// database / encryption stack. We then drive hasPermission via a spied // database / encryption stack. We then drive hasPermission via a spied
// getUserPermissions so we test the wildcard-matching logic in isolation. // getUserPermissions so we test the wildcard-matching logic in isolation.
vi.mock("../database/db/index.js", () => ({ db: {} })); vi.mock("../../database/db/index.js", () => ({ db: {} }));
vi.mock("./logger.js", () => ({ vi.mock("../../utils/logger.js", () => ({
databaseLogger: { databaseLogger: {
debug: vi.fn(), debug: vi.fn(),
info: vi.fn(), info: vi.fn(),
@@ -15,7 +15,7 @@ vi.mock("./logger.js", () => ({
}, },
})); }));
const { PermissionManager } = await import("./permission-manager.js"); const { PermissionManager } = await import("../../utils/permission-manager.js");
type PermissionManagerInstance = ReturnType< type PermissionManagerInstance = ReturnType<
typeof PermissionManager.getInstance typeof PermissionManager.getInstance
@@ -5,7 +5,7 @@ import {
getRequestBaseUrlWithForceHTTPS, getRequestBaseUrlWithForceHTTPS,
getRequestOrigin, getRequestOrigin,
normalizeBasePath, normalizeBasePath,
} from "./request-origin.js"; } from "../../utils/request-origin.js";
function request(headers: Record<string, string | string[] | undefined>) { function request(headers: Record<string, string | string[] | undefined>) {
return { return {
@@ -9,7 +9,7 @@ const state = vi.hoisted(() => ({
ownerCredential: null as Record<string, unknown> | null, ownerCredential: null as Record<string, unknown> | null,
})); }));
vi.mock("../database/repositories/factory.js", () => ({ vi.mock("../../database/repositories/factory.js", () => ({
createCurrentCredentialRepository: () => ({ createCurrentCredentialRepository: () => ({
findByIdForUser: async (_userId: string, _id: number) => findByIdForUser: async (_userId: string, _id: number) =>
state.ownerCredential, state.ownerCredential,
@@ -38,7 +38,7 @@ vi.mock("../database/repositories/factory.js", () => ({
}), }),
})); }));
vi.mock("./data-crypto.js", () => ({ vi.mock("../../utils/data-crypto.js", () => ({
DataCrypto: { DataCrypto: {
validateUserAccess: (userId: string) => { validateUserAccess: (userId: string) => {
if (userId === "owner") return ownerDEK; if (userId === "owner") return ownerDEK;
@@ -51,8 +51,8 @@ vi.mock("./data-crypto.js", () => ({
}, },
})); }));
import { FieldCrypto } from "./field-crypto.js"; import { FieldCrypto } from "../../utils/field-crypto.js";
import { SharedCredentialManager } from "./shared-credential-manager.js"; import { SharedCredentialManager } from "../../utils/shared-credential-manager.js";
const manager = SharedCredentialManager.getInstance(); const manager = SharedCredentialManager.getInstance();
@@ -6,7 +6,7 @@ import {
preparePrivateKeyForSSH2, preparePrivateKeyForSSH2,
getFriendlyKeyTypeName, getFriendlyKeyTypeName,
validateKeyPair, validateKeyPair,
} from "./ssh-key-utils.js"; } from "../../utils/ssh-key-utils.js";
// A real OpenSSH ed25519 keypair generated solely for these tests. It grants no // A real OpenSSH ed25519 keypair generated solely for these tests. It grants no
// access to anything and exists only so the ssh2 parsing path is exercised for // access to anything and exists only so the ssh2 parsing path is exercised for
@@ -4,7 +4,7 @@ import {
detectPlatform, detectPlatform,
parseUserAgent, parseUserAgent,
generateDeviceFingerprint, generateDeviceFingerprint,
} from "./user-agent-parser.js"; } from "../../utils/user-agent-parser.js";
function reqWith(headers: Record<string, string>): Request { function reqWith(headers: Record<string, string>): Request {
return { headers } as unknown as Request; return { headers } as unknown as Request;
@@ -1,5 +1,5 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { RawSqliteUserEncryptionMigrationStore } from "./user-encryption-migration-store.js"; import { RawSqliteUserEncryptionMigrationStore } from "../../utils/user-encryption-migration-store.js";
describe("RawSqliteUserEncryptionMigrationStore", () => { describe("RawSqliteUserEncryptionMigrationStore", () => {
function createDb() { function createDb() {
@@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
const settingsStore = new Map<string, string>(); const settingsStore = new Map<string, string>();
vi.mock("../database/repositories/factory.js", () => ({ vi.mock("../../database/repositories/factory.js", () => ({
getCurrentSettingValue: (key: string) => settingsStore.get(key) ?? null, getCurrentSettingValue: (key: string) => settingsStore.get(key) ?? null,
createCurrentSettingsRepository: () => ({ createCurrentSettingsRepository: () => ({
upsert: async (key: string, value: string) => { upsert: async (key: string, value: string) => {
@@ -19,7 +19,7 @@ import {
UserKeyManager, UserKeyManager,
UserKeyUnavailableError, UserKeyUnavailableError,
userDekSettingsKey, userDekSettingsKey,
} from "./user-keys.js"; } from "../../utils/user-keys.js";
const masterKey = crypto.randomBytes(32); const masterKey = crypto.randomBytes(32);
const manager = UserKeyManager.getInstance(); const manager = UserKeyManager.getInstance();
@@ -12,7 +12,11 @@ vi.mock("dgram", () => {
}); });
import dgram from "dgram"; import dgram from "dgram";
import { isValidMac, buildMagicPacket, sendWakeOnLan } from "./wake-on-lan.js"; import {
isValidMac,
buildMagicPacket,
sendWakeOnLan,
} from "../../utils/wake-on-lan.js";
describe("isValidMac", () => { describe("isValidMac", () => {
it("accepts colon-separated MAC addresses", () => { it("accepts colon-separated MAC addresses", () => {