mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
feat(shared): backfill-status and usage wire schemas on the storage admin state
This commit is contained in:
@@ -32,6 +32,8 @@ function baseState(overrides: Partial<StorageAdminState> = {}): StorageAdminStat
|
||||
health: { replicaFailures: [] },
|
||||
encryptionReady: true,
|
||||
seedFilePresent: false,
|
||||
usage: null,
|
||||
backfills: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ const STATE: StorageAdminState = {
|
||||
health: { replicaFailures: [] },
|
||||
encryptionReady: true,
|
||||
seedFilePresent: false,
|
||||
usage: null,
|
||||
backfills: [],
|
||||
};
|
||||
|
||||
describe('settingsDocumentOf', () => {
|
||||
|
||||
@@ -30,6 +30,8 @@ function baseState(overrides: Partial<StorageAdminState> = {}): StorageAdminStat
|
||||
health: { replicaFailures: [] },
|
||||
encryptionReady: true,
|
||||
seedFilePresent: false,
|
||||
usage: null,
|
||||
backfills: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -57,6 +57,9 @@ export class StorageAdminService {
|
||||
health: { replicaFailures: this.storage.health().replicaFailures.map((f) => ({ ...f })) },
|
||||
encryptionReady: this.env.env().security.encryptionKeySet,
|
||||
seedFilePresent: fs.existsSync(SEED_CONFIG_PATH),
|
||||
// Filled by the stats/jobs services — backfill/stats spec.
|
||||
usage: null,
|
||||
backfills: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,11 @@ import {
|
||||
STORAGE_BACKEND_TYPES,
|
||||
STORAGE_BACKEND_TYPE_IDS,
|
||||
STORAGE_CATEGORIES,
|
||||
storageAdminStateSchema,
|
||||
storageBackfillStatusSchema,
|
||||
storageConfigSchema,
|
||||
storageSecretFields,
|
||||
storageUsageSchema,
|
||||
type StorageBackendTypeId,
|
||||
} from './storage.schema';
|
||||
|
||||
@@ -145,3 +148,37 @@ describe('storageConfigSchema wire compatibility', () => {
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('backfill + usage wire shapes', () => {
|
||||
it('storageBackfillStatusSchema round-trips a running and a finished status', () => {
|
||||
const running = {
|
||||
backend: 'backups-local-mirror',
|
||||
status: 'running',
|
||||
done: 3,
|
||||
total: 10,
|
||||
copied: 2,
|
||||
skipped: 1,
|
||||
failed: 0,
|
||||
startedAt: 1_700_000_000_000,
|
||||
};
|
||||
expect(storageBackfillStatusSchema.parse(running)).toEqual(running);
|
||||
const done = { ...running, status: 'done', done: 10, finishedAt: 1_700_000_100_000 };
|
||||
expect(storageBackfillStatusSchema.parse(done)).toEqual(done);
|
||||
expect(storageBackfillStatusSchema.safeParse({ ...running, status: 'paused' }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('storageUsageSchema carries all configurable categories plus the legacy photos bucket', () => {
|
||||
const perCategory = { objects: 1, bytes: 2 };
|
||||
const usage = {
|
||||
computedAt: 1_700_000_000_000,
|
||||
categories: Object.fromEntries(STORAGE_CATEGORIES.map((c) => [c, perCategory])),
|
||||
legacyPhotos: { objects: 0, bytes: 0 },
|
||||
};
|
||||
expect(storageUsageSchema.parse(usage)).toEqual(usage);
|
||||
});
|
||||
|
||||
it('the admin state embeds usage (nullable) and backfills', () => {
|
||||
const stateShape = storageAdminStateSchema.pick({ usage: true, backfills: true });
|
||||
expect(stateShape.parse({ usage: null, backfills: [] })).toEqual({ usage: null, backfills: [] });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -212,6 +212,33 @@ export const storageReplicaFailureSchema = z.object({
|
||||
at: z.number(),
|
||||
});
|
||||
|
||||
// ── Backfill + usage (backfill/stats/notifications spec) ─────────────────────
|
||||
|
||||
export const storageBackfillStatusSchema = z.object({
|
||||
/** The mirror's wire name — user-facing copy says "Sync", never this. */
|
||||
backend: z.string(),
|
||||
status: z.enum(['running', 'done', 'error', 'cancelled']),
|
||||
/** Objects examined in the copy phase; total is 0 while enumerating. */
|
||||
done: z.number(),
|
||||
total: z.number(),
|
||||
copied: z.number(),
|
||||
skipped: z.number(),
|
||||
failed: z.number(),
|
||||
startedAt: z.number(),
|
||||
finishedAt: z.number().optional(),
|
||||
/** status 'error' only: the aborting (primary-side) error. */
|
||||
error: z.string().optional(),
|
||||
});
|
||||
export type StorageBackfillStatus = z.infer<typeof storageBackfillStatusSchema>;
|
||||
|
||||
export const storageUsageSchema = z.object({
|
||||
computedAt: z.number(),
|
||||
categories: z.record(storageCategorySchema, z.object({ objects: z.number(), bytes: z.number() })),
|
||||
/** The served-legacy photos directory — attributed to whatever backend `photos` resolves to. */
|
||||
legacyPhotos: z.object({ objects: z.number(), bytes: z.number() }),
|
||||
});
|
||||
export type StorageUsage = z.infer<typeof storageUsageSchema>;
|
||||
|
||||
export const storageAdminStateSchema = z.object({
|
||||
backends: z.array(
|
||||
z.object({
|
||||
@@ -231,6 +258,8 @@ export const storageAdminStateSchema = z.object({
|
||||
health: z.object({ replicaFailures: z.array(storageReplicaFailureSchema) }),
|
||||
encryptionReady: z.boolean(),
|
||||
seedFilePresent: z.boolean(),
|
||||
usage: storageUsageSchema.nullable(),
|
||||
backfills: z.array(storageBackfillStatusSchema),
|
||||
});
|
||||
export type StorageAdminState = z.infer<typeof storageAdminStateSchema>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user