mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
A full backup carries the account's own mood tags, rated factors and tag groups, and the JSON import left them out: a mood entry imported into another account landed without every link to a tag the person had made. Before the entries are written, every key they or the file name is resolved once. A catalogue tag or one the account owns is used as it is; an own tag whose label the file states differently is kept, never overwritten, and named in keptMoodTagKeys (keptMoodCategoryKeys for a group). A tag the file defines and the account lacks is created in its group, the group created the same way, within the account's caps of 50 tags and 12 groups. A key another account of the server holds comes over under a key derived from the account and the file's key, so a second import of the same file finds the copy instead of minting another. The links are written through that resolution, own rated factors included, inside the entry's transaction; anything unresolved is still dropped and named. The import card names kept keys in all nine locales. The field-coverage guard now holds the backup's customMoodTags and customMoodTagCategories to the import schema. The round trip moves a backup to another server (the same seeded catalogue, none of the account's rows), imports it into a fresh account and compares the re-export, own tags, group and rated factor included; a second case imports into another account of the same server, and a third keeps a renamed own tag.
91 lines
3.0 KiB
TypeScript
91 lines
3.0 KiB
TypeScript
/**
|
|
* The published JSON import contract names the fields the import reads.
|
|
*
|
|
* `src/lib/openapi/routes/import.ts` restates `jsonImportSchema` by hand,
|
|
* because the runtime schema carries transforms the document describes in
|
|
* prose. A restatement drifts: a field the import learns to read stays
|
|
* undocumented, and one it stops reading stays promised. So the two field
|
|
* sets are compared here, per row kind, both ways.
|
|
*/
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
jsonImportMeasurementSchema,
|
|
jsonImportMoodContextSchema,
|
|
jsonImportMoodEntrySchema,
|
|
jsonImportMoodTagCategorySchema,
|
|
jsonImportMoodTagSchema,
|
|
jsonImportSchema,
|
|
} from "@/lib/import/json/schema";
|
|
import { buildOpenApiDocument } from "@/lib/openapi/registry";
|
|
|
|
type SchemaObject = {
|
|
properties?: Record<string, SchemaObject>;
|
|
$ref?: string;
|
|
allOf?: SchemaObject[];
|
|
};
|
|
|
|
const doc = buildOpenApiDocument() as {
|
|
components: { schemas: Record<string, SchemaObject> };
|
|
};
|
|
|
|
function resolve(schema: SchemaObject): SchemaObject {
|
|
if (schema.$ref) {
|
|
const name = schema.$ref.split("/").pop()!;
|
|
return resolve(doc.components.schemas[name]!);
|
|
}
|
|
if (schema.allOf) {
|
|
return {
|
|
properties: Object.assign(
|
|
{},
|
|
...schema.allOf.map((part) => resolve(part).properties ?? {}),
|
|
),
|
|
};
|
|
}
|
|
return schema;
|
|
}
|
|
|
|
function documented(name: string): string[] {
|
|
const schema = doc.components.schemas[name];
|
|
expect(schema, `${name} is published`).toBeDefined();
|
|
return Object.keys(resolve(schema!).properties ?? {}).sort();
|
|
}
|
|
|
|
describe("the published JSON import contract", () => {
|
|
it("names every measurement field the import reads, and no other", () => {
|
|
const fields = documented("JsonImportMeasurement");
|
|
expect(fields.length).toBeGreaterThan(5);
|
|
expect(fields).toEqual(
|
|
Object.keys(jsonImportMeasurementSchema.shape).sort(),
|
|
);
|
|
});
|
|
|
|
it("names every mood-entry field the import reads, and no other", () => {
|
|
const fields = documented("JsonImportMoodEntry");
|
|
expect(fields.length).toBeGreaterThan(5);
|
|
expect(fields).toEqual(Object.keys(jsonImportMoodEntrySchema.shape).sort());
|
|
});
|
|
|
|
it("names every section and every vocabulary field the import reads", () => {
|
|
expect(documented("JsonImportRequest")).toEqual(
|
|
Object.keys(jsonImportSchema.shape).sort(),
|
|
);
|
|
expect(documented("JsonImportMoodTag")).toEqual(
|
|
Object.keys(jsonImportMoodTagSchema.shape).sort(),
|
|
);
|
|
expect(documented("JsonImportMoodTagCategory")).toEqual(
|
|
Object.keys(jsonImportMoodTagCategorySchema.shape).sort(),
|
|
);
|
|
});
|
|
|
|
it("names every day-context field the import reads, and no other", () => {
|
|
const entry = resolve(doc.components.schemas.JsonImportMoodEntry!);
|
|
const context = resolve(entry.properties!.context!);
|
|
const fields = Object.keys(context.properties ?? {}).sort();
|
|
expect(fields.length).toBeGreaterThan(10);
|
|
expect(fields).toEqual(
|
|
Object.keys(jsonImportMoodContextSchema.shape).sort(),
|
|
);
|
|
});
|
|
});
|