@serfab/cadre-host 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
1
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
2
  import { tmpdir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
 
@@ -180,15 +180,16 @@ describe('/api/settings routes', () => {
180
180
  expect(res.statusCode).toBe(400);
181
181
  });
182
182
 
183
- it('reads v1 host.config.json and migrates to v2 on first read', async () => {
184
- writeV1Config(dataDir);
183
+ it('rejects a v1 host.config.json without rewriting it', async () => {
184
+ const path = writeV1Config(dataDir);
185
+ const raw = readFileSync(path, 'utf8');
185
186
  app = Fastify();
186
187
  registerErrorHandler(app);
187
188
  registerSettingsRoutes(app, { settingsStore: new HostSettingsStore({ dataDir }), nat: fakeNat().svc });
188
189
  const res = await app.inject({ method: 'GET', url: '/api/settings' });
189
- expect(res.statusCode).toBe(200);
190
- const body = res.json() as { data: { version: number; updates: { autoApply: boolean } } };
191
- expect(body.data.version).toBe(2);
192
- expect(body.data.updates.autoApply).toBe(false);
190
+ expect(res.statusCode).toBe(500);
191
+ const body = res.json() as { error: { message: string } };
192
+ expect(body.error.message).toMatch(/unsupported version=1/);
193
+ expect(readFileSync(path, 'utf8')).toBe(raw);
193
194
  });
194
195
  });
@@ -1,37 +1,41 @@
1
- /**
2
- * Settings store — read-write facade around `host.config.json` for the
3
- * /api/settings route.
4
- *
5
- * The installer (`6.4.1`) owns the schema (`HostConfigFile` v2). This module
6
- * just delegates load/persist to that file's helpers; it exists so route
7
- * handlers can read/write without each one re-resolving `configPath(dataDir)`.
8
- *
9
- * PUT semantics live in routes/settings.ts; this class is purely an I/O
10
- * boundary so tests can swap it for an in-memory fake.
11
- */
12
-
13
- import { configPath } from '../installer/paths.js';
14
- import { readHostConfig, updateHostConfig, type HostConfigFile } from '../installer/config.js';
15
-
16
- export interface HostSettingsStoreOptions {
17
- /** Resolved cadre-host data dir. */
18
- dataDir: string;
19
- }
20
-
21
- export class HostSettingsStore {
22
- private readonly configPath: string;
23
-
24
- constructor(opts: HostSettingsStoreOptions) {
25
- this.configPath = configPath(opts.dataDir);
26
- }
27
-
28
- /** Read the current host.config.json (runs v1→v2 migration on first read). */
29
- read(): HostConfigFile {
30
- return readHostConfig(this.configPath);
31
- }
32
-
33
- /** Patch and persist. */
34
- update(patch: Partial<Omit<HostConfigFile, 'version'>>): HostConfigFile {
35
- return updateHostConfig(this.configPath, patch);
36
- }
37
- }
1
+ /**
2
+ * Settings store — read-write facade around `host.config.json` for the
3
+ * /api/settings route.
4
+ *
5
+ * The installer (`6.4.1`) owns the schema (`HostConfigFile` v2). This module
6
+ * just delegates load/persist to that file's helpers; it exists so route
7
+ * handlers can read/write without each one re-resolving `configPath(dataDir)`.
8
+ *
9
+ * PUT semantics live in routes/settings.ts; this class is purely an I/O
10
+ * boundary so tests can swap it for an in-memory fake.
11
+ */
12
+
13
+ import { configPath } from '../installer/paths.js';
14
+ import { readHostConfig, updateHostConfig, type HostConfigFile } from '../installer/config.js';
15
+
16
+ export interface HostSettingsStoreOptions {
17
+ /** Resolved cadre-host data dir. */
18
+ dataDir: string;
19
+ }
20
+
21
+ export class HostSettingsStore {
22
+ private readonly configPath: string;
23
+
24
+ constructor(opts: HostSettingsStoreOptions) {
25
+ this.configPath = configPath(opts.dataDir);
26
+ }
27
+
28
+ // NOTE: a bad config (absent, malformed, wrong version) throws a plain Error, so
29
+ // /api/settings answers 500 code "internal" with the read message passed through.
30
+ // Fine while the UI just surfaces the text; if it ever needs to tell "your config
31
+ // file is unreadable" apart from a genuine server bug, give this a typed error.
32
+ /** Read the current host.config.json. Throws if absent, malformed, or not version 2. */
33
+ read(): HostConfigFile {
34
+ return readHostConfig(this.configPath);
35
+ }
36
+
37
+ /** Patch and persist. */
38
+ update(patch: Partial<Omit<HostConfigFile, 'version'>>): HostConfigFile {
39
+ return updateHostConfig(this.configPath, patch);
40
+ }
41
+ }