feat(api): complete NaxOS management daemon, GitOps engine, ZFS controller, and test suite
CI & Test NaxOS Management API / test (push) Failing after 2m49s

This commit is contained in:
Lukas Holzner
2026-09-03 23:56:02 +02:00
parent 2f42656c2d
commit a283feedbd
27 changed files with 4882 additions and 2 deletions
+42
View File
@@ -0,0 +1,42 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { GitOpsService } from '../src/services/gitops.service.js';
import { AppsService } from '../src/services/apps.service.js';
import os from 'os';
import path from 'path';
import fs from 'fs/promises';
describe('AppsService', () => {
let gitops: GitOpsService;
let apps: AppsService;
let testRepoDir: string;
beforeEach(async () => {
testRepoDir = path.join(os.tmpdir(), `naxos-apps-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
gitops = new GitOpsService(testRepoDir);
await gitops.init();
apps = new AppsService(gitops);
});
afterEach(async () => {
try {
await fs.rm(testRepoDir, { recursive: true, force: true });
} catch {}
});
it('should return the curated app catalog', () => {
const catalog = apps.getCatalog();
expect(catalog.length).toBeGreaterThanOrEqual(5);
const immich = catalog.find((c) => c.id === 'immich');
expect(immich).toBeDefined();
expect(immich?.recommendedRuntime).toBe('systemd');
});
it('should install app into active workloads', async () => {
const installed = await apps.installApp({ appId: 'vaultwarden' });
expect(installed.appId).toBe('vaultwarden');
expect(installed.status).toBe('running');
const list = apps.getInstalledApps();
expect(list.some((a) => a.appId === 'vaultwarden')).toBe(true);
});
});
+51
View File
@@ -0,0 +1,51 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { GitOpsService } from '../src/services/gitops.service.js';
import os from 'os';
import path from 'path';
import fs from 'fs/promises';
describe('GitOpsService', () => {
let gitops: GitOpsService;
let testRepoDir: string;
beforeEach(async () => {
testRepoDir = path.join(os.tmpdir(), `naxos-gitops-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
gitops = new GitOpsService(testRepoDir);
await gitops.init();
});
afterEach(async () => {
try {
await fs.rm(testRepoDir, { recursive: true, force: true });
} catch {}
});
it('should initialize and produce valid GitOps status', async () => {
const status = await gitops.getStatus();
expect(status.enabled).toBe(true);
expect(status.branch).toBe('main');
expect(status.lastCommitSha).toBeDefined();
});
it('should translate declarative configuration into valid NixOS module code', async () => {
const nixCode = await gitops.renderNixOSModule();
expect(nixCode).toContain('services.naxos.storage');
expect(nixCode).toContain('services.naxos.shares.samba');
expect(nixCode).toContain('importExistingPools');
expect(nixCode).toContain('tank');
});
it('should create commits on state changes', async () => {
const res = await gitops.updateConfig(
{
core: { hostname: 'naxos-test', timezone: 'UTC' },
},
'feat(test): updated hostname'
);
expect(res.commitSha).toBeDefined();
expect(res.commitSha.length).toBeGreaterThan(10);
const commits = await gitops.getCommits(5);
expect(commits[0].message).toBe('feat(test): updated hostname');
});
});
+52
View File
@@ -0,0 +1,52 @@
import { describe, it, expect } from 'vitest';
import { ZfsService } from '../src/services/zfs.service.js';
describe('ZfsService', () => {
const zfs = new ZfsService();
it('should list configured ZFS pools with health status', async () => {
const pools = await zfs.listPools();
expect(pools).toBeDefined();
expect(pools.length).toBeGreaterThan(0);
const tank = pools.find((p) => p.name === 'tank');
expect(tank).toBeDefined();
expect(tank?.health).toBe('ONLINE');
expect(tank?.capacityPercent).toBeGreaterThanOrEqual(0);
});
it('should list datasets with proper mountpoint and compression', async () => {
const datasets = await zfs.listDatasets('tank');
expect(datasets.length).toBeGreaterThan(0);
const photos = datasets.find((d) => d.name === 'tank/media/photos');
expect(photos).toBeDefined();
expect(photos?.mountpoint).toBe('/tank/media/photos');
expect(photos?.compression).toBe('zstd');
});
it('should scan for unimported foreign pools without data loss', async () => {
const unimported = await zfs.scanUnimportedPools();
expect(unimported).toBeDefined();
expect(Array.isArray(unimported)).toBe(true);
});
it('should safely import a foreign pool', async () => {
const res = await zfs.importPool({ poolName: 'truenas_pool', force: true, noMount: true });
expect(res.success).toBe(true);
const pools = await zfs.listPools();
const imported = pools.find((p) => p.name === 'truenas_pool');
expect(imported).toBeDefined();
});
it('should create and rollback snapshots', async () => {
const snapTag = `test-${Date.now()}`;
const createRes = await zfs.createSnapshot('tank/media/photos', snapTag);
expect(createRes.success).toBe(true);
const snapshots = await zfs.listSnapshots('tank/media/photos');
const created = snapshots.find((s) => s.snapshotTag === snapTag);
expect(created).toBeDefined();
const rollbackRes = await zfs.rollbackSnapshot(`tank/media/photos@${snapTag}`);
expect(rollbackRes.success).toBe(true);
});
});