feat: redesign user host defaults system

This commit is contained in:
LukeGus committed 2026-09-30 06:19:56 -05:00
1 parent 3028ab1176
commit a4a4d4f27b
144 files changed
+24609 -2555

No files matched your search

@@ -65,6 +65,9 @@ vi.mock("../../utils/permission-manager.js", () => ({
},
}));
vi.mock("../../database/repositories/factory.js", () => ({
createCurrentHostDefaultsRepository: () => ({
listHosts: async () => [],
}),
createCurrentHostResolutionRepository: () => ({
findHostsByUserId: async () => h.ownedHosts,
listHostRowsForAccessList: async () => h.sharedRows,
@@ -185,6 +188,14 @@ vi.mock("../../utils/shared-host-secrets-manager.js", () => ({
},
}));
vi.mock("../../hosts/defaults/index.js", () => ({
applyHostDefaultsToWrite: async () => ({ core: [] }),
applyDefaultsAfterHostWrite: async () => {},
}));
vi.mock("../../hosts/defaults/overrides.js", () => ({
changeHostOverrides: async () => {},
}));
vi.mock("../../hosts/delete-host.js", () => ({
deleteOwnedHost: async (userId: string, hostId: number) => {
h.deleted.push({ userId, hostId });
@@ -77,7 +77,14 @@ vi.mock("../../../utils/permission-manager.js", () => ({
}),
},
}));
vi.mock("../../../hosts/defaults/index.js", () => ({
applyHostDefaultsToWrite: async () => ({ core: [] }),
applyDefaultsAfterHostWrite: async () => {},
}));
vi.mock("../../../database/repositories/factory.js", () => ({
createCurrentHostDefaultsRepository: () => ({
listHosts: async () => [],
}),
createCurrentHostRepository: () => ({
createEncryptedForUser: async () => secretHost(1),
updateEncryptedForUser: async () => secretHost(1),
@@ -58,6 +58,10 @@ const UNGATED: Record<string, string> = {
"settings.getUser": "the plugin's own user settings",
"settings.setUser": "the plugin's own user settings",
"settings.getHost": "the plugin's own host settings",
"settings.getHostFor":
"the plugin's own host settings, as one user sees them",
"settings.getHostDefault":
"the plugin's own host settings, as a user's defaults",
"settings.setHost": "the plugin's own host settings",
"settings.getAll": "the plugin's own settings",
"settings.onChange": "the plugin's own settings",
@@ -418,14 +418,62 @@ describe("host field options", () => {
).toEqual([]);
});
it("rejects a defaultFrom that names a secret or unknown admin field", () => {
for (const defaultFrom of ["token", "missing"]) {
expect(
validateManifest(
host([{ key: "mode", type: "boolean", labelKey: "k", defaultFrom }]),
).join(),
).toMatch(/defaultFrom/);
}
it("still accepts an old defaultFrom, but only as a string", () => {
expect(
validateManifest(
host([
{
key: "mode",
type: "boolean",
labelKey: "k",
defaultFrom: "missing",
},
]),
),
).toEqual([]);
expect(
validateManifest(
host([{ key: "mode", type: "boolean", labelKey: "k", defaultFrom: 1 }]),
).join(),
).toMatch(/defaultFrom/);
});
it("accepts defaultable, defaultLevels and personal on host fields", () => {
expect(
validateManifest(
host([
{
key: "mode",
type: "boolean",
labelKey: "k",
defaultable: false,
defaultLevels: ["user", "folder"],
personal: true,
},
]),
),
).toEqual([]);
});
it("rejects bad default options and a defaultable secret", () => {
const errors = (field: Record<string, unknown>) =>
validateManifest(
host([{ key: "mode", type: "boolean", labelKey: "k", ...field }]),
).join();
expect(errors({ defaultable: "yes" })).toMatch(/defaultable/);
expect(errors({ personal: 1 })).toMatch(/personal/);
expect(errors({ defaultLevels: [] })).toMatch(/defaultLevels/);
expect(errors({ defaultLevels: ["host"] })).toMatch(/defaultLevels/);
expect(errors({ type: "secret", defaultable: true })).toMatch(
/cannot be defaultable/,
);
expect(
validateManifest(
withSettings({
user: [{ key: "a", type: "boolean", labelKey: "k", personal: true }],
}),
).join(),
).toMatch(/only valid on host fields/);
});
it("rejects an unknown shareRead level and host options on admin fields", () => {
@@ -24,6 +24,9 @@ vi.mock("../../utils/crypto-migration/raw-rows.js", () => ({
runStatement: vi.fn(async () => {}),
}));
vi.mock("../../hosts/defaults/overrides.js", () => ({
changeHostOverrides: async () => {},
}));
vi.mock("../../utils/logger.js", () => {
const logger = {
debug: vi.fn(),
@@ -127,11 +127,13 @@ describe("core sync entities", () => {
it("registers what core syncs, under the wire names 2.8 used", () => {
expect(listEntityTypes()).toEqual([
"accountProfile",
"hostDefaultsAdmin",
"sshCredentials",
"sharedCredentials",
"sshFolders",
"hosts",
"sharedHosts",
"hostDefaults",
"userPreferences",
"pluginUserSettings",
]);
@@ -168,7 +170,11 @@ describe("core sync entities", () => {
.filter((entity) => entity.singleton)
.map((entity) => entity.type);
expect(singletons.sort()).toEqual(["accountProfile", "userPreferences"]);
expect(singletons.sort()).toEqual([
"accountProfile",
"hostDefaultsAdmin",
"userPreferences",
]);
});
it("only sends shared copies and the account one way", () => {
@@ -178,6 +184,7 @@ describe("core sync entities", () => {
expect(readOnly.sort()).toEqual([
"accountProfile",
"hostDefaultsAdmin",
"sharedCredentials",
"sharedHosts",
]);