@axiom-lattice/pg-stores 5.0.0 → 5.0.1
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.
- package/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +172 -0
- package/dist/index.js +8 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +14 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/PostgreSQLAssistantStore.integration.test.ts +225 -0
- package/src/__tests__/PostgreSQLDatabaseConfigStore.integration.test.ts +210 -0
- package/src/__tests__/PostgreSQLEvalStore.integration.test.ts +575 -0
- package/src/__tests__/PostgreSQLMcpServerConfigStore.integration.test.ts +258 -0
- package/src/__tests__/PostgreSQLMetricsServerConfigStore.integration.test.ts +216 -0
- package/src/__tests__/PostgreSQLModelProviderStore.integration.test.ts +313 -0
- package/src/__tests__/PostgreSQLSkillStore.integration.test.ts +203 -0
- package/src/__tests__/PostgreSQLWorkflowTrackingStore.integration.test.ts +447 -0
- package/src/stores/PostgreSQLEvalStore.ts +3 -3
- package/src/stores/PostgreSQLSkillStore.ts +1 -1
- package/src/stores/PostgreSQLWorkflowTrackingStore.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiom-lattice/pg-stores",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.1",
|
|
4
4
|
"description": "PG stores implementation for Axiom Lattice framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"@langchain/core": "1.3.2",
|
|
26
26
|
"pg": "^8.16.3",
|
|
27
27
|
"uuid": "^9.0.1",
|
|
28
|
-
"@axiom-lattice/core": "12.0.
|
|
29
|
-
"@axiom-lattice/protocols": "7.0.
|
|
28
|
+
"@axiom-lattice/core": "12.0.1",
|
|
29
|
+
"@axiom-lattice/protocols": "7.0.1"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/jest": "^29.5.14",
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { Pool } from "pg";
|
|
3
|
+
|
|
4
|
+
import { PostgreSQLAssistantStore } from "../stores/PostgreSQLAssistantStore";
|
|
5
|
+
|
|
6
|
+
const TEST_DATABASE_URL = process.env.TEST_DATABASE_URL;
|
|
7
|
+
const describeIfDatabase = TEST_DATABASE_URL ? describe : describe.skip;
|
|
8
|
+
|
|
9
|
+
describeIfDatabase("PostgreSQLAssistantStore integration", () => {
|
|
10
|
+
let store: PostgreSQLAssistantStore;
|
|
11
|
+
let pool: Pool;
|
|
12
|
+
const tenants = new Set<string>();
|
|
13
|
+
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
store = new PostgreSQLAssistantStore({
|
|
16
|
+
poolConfig: TEST_DATABASE_URL!,
|
|
17
|
+
autoMigrate: true,
|
|
18
|
+
});
|
|
19
|
+
await store.initialize();
|
|
20
|
+
pool = new Pool({ connectionString: TEST_DATABASE_URL });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(async () => {
|
|
24
|
+
if (tenants.size === 0) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
await pool.query("DELETE FROM lattice_assistants WHERE tenant_id = ANY($1::text[])", [
|
|
28
|
+
[...tenants],
|
|
29
|
+
]);
|
|
30
|
+
tenants.clear();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterAll(async () => {
|
|
34
|
+
await pool.end();
|
|
35
|
+
await store.dispose();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
function tenantId(): string {
|
|
39
|
+
const id = `assistant-${randomUUID()}`;
|
|
40
|
+
tenants.add(id);
|
|
41
|
+
return id;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function graphDefinition(label: string) {
|
|
45
|
+
return {
|
|
46
|
+
entryPoint: "orchestrator",
|
|
47
|
+
nodes: [
|
|
48
|
+
{ id: "orchestrator", type: "agent", metadata: { tags: ["a", "b"], nested: { level: 2 } } },
|
|
49
|
+
{ id: "worker", type: "tool", metadata: { disabled: false, note: null } },
|
|
50
|
+
],
|
|
51
|
+
edges: [{ from: "orchestrator", to: "worker", condition: null }],
|
|
52
|
+
metadata: { label, flags: { beta: true } },
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
it("creates an assistant and reads it back by id", async () => {
|
|
57
|
+
const tenant = tenantId();
|
|
58
|
+
const id = randomUUID();
|
|
59
|
+
|
|
60
|
+
const created = await store.createAssistant(tenant, id, {
|
|
61
|
+
name: "Planner",
|
|
62
|
+
description: "Plans work",
|
|
63
|
+
ownerUserId: "user-1",
|
|
64
|
+
graphDefinition: graphDefinition("v1"),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(created).toMatchObject({
|
|
68
|
+
id,
|
|
69
|
+
tenantId: tenant,
|
|
70
|
+
name: "Planner",
|
|
71
|
+
description: "Plans work",
|
|
72
|
+
ownerUserId: "user-1",
|
|
73
|
+
});
|
|
74
|
+
expect(created.graphDefinition).toEqual(graphDefinition("v1"));
|
|
75
|
+
expect(created.createdAt).toBeInstanceOf(Date);
|
|
76
|
+
expect(created.updatedAt).toBeInstanceOf(Date);
|
|
77
|
+
|
|
78
|
+
const read = await store.getAssistantById(tenant, id);
|
|
79
|
+
expect(read).toMatchObject({
|
|
80
|
+
id,
|
|
81
|
+
tenantId: tenant,
|
|
82
|
+
name: "Planner",
|
|
83
|
+
description: "Plans work",
|
|
84
|
+
ownerUserId: "user-1",
|
|
85
|
+
});
|
|
86
|
+
expect(read!.graphDefinition.metadata).toEqual({ label: "v1", flags: { beta: true } });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("lists only the requested tenant's assistants", async () => {
|
|
90
|
+
const tenantA = tenantId();
|
|
91
|
+
const tenantB = tenantId();
|
|
92
|
+
const a1 = randomUUID();
|
|
93
|
+
const a2 = randomUUID();
|
|
94
|
+
const b1 = randomUUID();
|
|
95
|
+
await store.createAssistant(tenantA, a1, { name: "A1", graphDefinition: graphDefinition("a1") });
|
|
96
|
+
await store.createAssistant(tenantA, a2, { name: "A2", graphDefinition: graphDefinition("a2") });
|
|
97
|
+
await store.createAssistant(tenantB, b1, { name: "B1", graphDefinition: graphDefinition("b1") });
|
|
98
|
+
|
|
99
|
+
const listedA = await store.getAllAssistants(tenantA);
|
|
100
|
+
expect(listedA.map((a) => a.id).sort()).toEqual([a1, a2].sort());
|
|
101
|
+
expect(listedA.every((a) => a.tenantId === tenantA)).toBe(true);
|
|
102
|
+
|
|
103
|
+
const listedB = await store.getAllAssistants(tenantB);
|
|
104
|
+
expect(listedB.map((a) => a.id)).toEqual([b1]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("updates only the provided fields", async () => {
|
|
108
|
+
const tenant = tenantId();
|
|
109
|
+
const id = randomUUID();
|
|
110
|
+
await store.createAssistant(tenant, id, {
|
|
111
|
+
name: "Before",
|
|
112
|
+
description: "keep me",
|
|
113
|
+
graphDefinition: graphDefinition("before"),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const updated = await store.updateAssistant(tenant, id, {
|
|
117
|
+
name: "After",
|
|
118
|
+
ownerUserId: "user-2",
|
|
119
|
+
graphDefinition: graphDefinition("after"),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
expect(updated).toMatchObject({
|
|
123
|
+
id,
|
|
124
|
+
name: "After",
|
|
125
|
+
description: "keep me",
|
|
126
|
+
ownerUserId: "user-2",
|
|
127
|
+
});
|
|
128
|
+
expect(updated!.graphDefinition.metadata).toEqual({ label: "after", flags: { beta: true } });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("returns null / false for an absent assistant id", async () => {
|
|
132
|
+
const tenant = tenantId();
|
|
133
|
+
const missing = randomUUID();
|
|
134
|
+
|
|
135
|
+
expect(await store.getAssistantById(tenant, missing)).toBeNull();
|
|
136
|
+
expect(await store.hasAssistant(tenant, missing)).toBe(false);
|
|
137
|
+
expect(await store.updateAssistant(tenant, missing, { name: "nope" })).toBeNull();
|
|
138
|
+
expect(await store.deleteAssistant(tenant, missing)).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("deletes an assistant and reports existence", async () => {
|
|
142
|
+
const tenant = tenantId();
|
|
143
|
+
const id = randomUUID();
|
|
144
|
+
await store.createAssistant(tenant, id, { name: "Temp", graphDefinition: graphDefinition("temp") });
|
|
145
|
+
|
|
146
|
+
expect(await store.hasAssistant(tenant, id)).toBe(true);
|
|
147
|
+
expect(await store.deleteAssistant(tenant, id)).toBe(true);
|
|
148
|
+
expect(await store.hasAssistant(tenant, id)).toBe(false);
|
|
149
|
+
expect(await store.getAssistantById(tenant, id)).toBeNull();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("maps absent optional description and owner to undefined", async () => {
|
|
153
|
+
const tenant = tenantId();
|
|
154
|
+
const id = randomUUID();
|
|
155
|
+
await store.createAssistant(tenant, id, {
|
|
156
|
+
name: "Shared",
|
|
157
|
+
graphDefinition: { nested: { withNull: null }, list: [1, "two", false] },
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const read = await store.getAssistantById(tenant, id);
|
|
161
|
+
expect(read).toMatchObject({ id, name: "Shared" });
|
|
162
|
+
expect(read!.description).toBeUndefined();
|
|
163
|
+
expect(read!.ownerUserId).toBeUndefined();
|
|
164
|
+
expect(read!.graphDefinition).toEqual({ nested: { withNull: null }, list: [1, "two", false] });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("finds a personal assistant by owner, scoped to the tenant", async () => {
|
|
168
|
+
const tenant = tenantId();
|
|
169
|
+
const otherTenant = tenantId();
|
|
170
|
+
const personal = randomUUID();
|
|
171
|
+
const shared = randomUUID();
|
|
172
|
+
await store.createAssistant(tenant, personal, {
|
|
173
|
+
name: "Personal",
|
|
174
|
+
ownerUserId: "user-1",
|
|
175
|
+
graphDefinition: graphDefinition("personal"),
|
|
176
|
+
});
|
|
177
|
+
await store.createAssistant(tenant, shared, {
|
|
178
|
+
name: "Shared",
|
|
179
|
+
graphDefinition: graphDefinition("shared"),
|
|
180
|
+
});
|
|
181
|
+
await store.createAssistant(otherTenant, randomUUID(), {
|
|
182
|
+
name: "Other tenant personal",
|
|
183
|
+
ownerUserId: "user-1",
|
|
184
|
+
graphDefinition: graphDefinition("other"),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const found = await store.getByOwner(tenant, "user-1");
|
|
188
|
+
expect(found).toMatchObject({ id: personal, tenantId: tenant, ownerUserId: "user-1" });
|
|
189
|
+
|
|
190
|
+
expect(await store.getByOwner(tenant, "user-2")).toBeNull();
|
|
191
|
+
expect(await store.getByOwner(otherTenant, "user-2")).toBeNull();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("keeps the same assistant id independent across tenants", async () => {
|
|
195
|
+
const tenantA = tenantId();
|
|
196
|
+
const tenantB = tenantId();
|
|
197
|
+
const id = randomUUID();
|
|
198
|
+
await store.createAssistant(tenantA, id, { name: "A", graphDefinition: graphDefinition("a") });
|
|
199
|
+
await store.createAssistant(tenantB, id, { name: "B", graphDefinition: graphDefinition("b") });
|
|
200
|
+
|
|
201
|
+
expect((await store.getAssistantById(tenantA, id))?.name).toBe("A");
|
|
202
|
+
expect((await store.getAssistantById(tenantB, id))?.name).toBe("B");
|
|
203
|
+
expect((await store.getAllAssistants(tenantA)).map((a) => a.id)).toEqual([id]);
|
|
204
|
+
|
|
205
|
+
expect(await store.deleteAssistant(tenantA, id)).toBe(true);
|
|
206
|
+
expect(await store.hasAssistant(tenantA, id)).toBe(false);
|
|
207
|
+
expect(await store.hasAssistant(tenantB, id)).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("upserts when create is called again for the same id and tenant", async () => {
|
|
211
|
+
const tenant = tenantId();
|
|
212
|
+
const id = randomUUID();
|
|
213
|
+
await store.createAssistant(tenant, id, { name: "First", graphDefinition: graphDefinition("first") });
|
|
214
|
+
const second = await store.createAssistant(tenant, id, {
|
|
215
|
+
name: "Second",
|
|
216
|
+
description: "replaced",
|
|
217
|
+
graphDefinition: graphDefinition("second"),
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
expect(second.name).toBe("Second");
|
|
221
|
+
const read = await store.getAssistantById(tenant, id);
|
|
222
|
+
expect(read).toMatchObject({ id, name: "Second", description: "replaced" });
|
|
223
|
+
expect(await store.getAllAssistants(tenant)).toHaveLength(1);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { Pool } from "pg";
|
|
3
|
+
|
|
4
|
+
// The @axiom-lattice/core barrel cannot load under jest in this package (ESM-only
|
|
5
|
+
// transitive deps), so mock it with the real encryption module — node:crypto only.
|
|
6
|
+
jest.mock("@axiom-lattice/core", () => ({
|
|
7
|
+
...jest.requireActual("../../../core/src/util/encryption"),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
import type { DatabaseConfig } from "@axiom-lattice/protocols";
|
|
11
|
+
import { decrypt } from "@axiom-lattice/core";
|
|
12
|
+
import { PostgreSQLDatabaseConfigStore } from "../stores/PostgreSQLDatabaseConfigStore";
|
|
13
|
+
|
|
14
|
+
const TEST_DATABASE_URL = process.env.TEST_DATABASE_URL;
|
|
15
|
+
const describeIfDatabase = TEST_DATABASE_URL ? describe : describe.skip;
|
|
16
|
+
|
|
17
|
+
describeIfDatabase("PostgreSQLDatabaseConfigStore integration", () => {
|
|
18
|
+
let store: PostgreSQLDatabaseConfigStore;
|
|
19
|
+
let pool: Pool;
|
|
20
|
+
|
|
21
|
+
beforeAll(async () => {
|
|
22
|
+
store = new PostgreSQLDatabaseConfigStore({
|
|
23
|
+
poolConfig: TEST_DATABASE_URL!,
|
|
24
|
+
autoMigrate: true,
|
|
25
|
+
});
|
|
26
|
+
await store.initialize();
|
|
27
|
+
pool = new Pool({ connectionString: TEST_DATABASE_URL });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const tenants = new Set<string>();
|
|
31
|
+
|
|
32
|
+
afterEach(async () => {
|
|
33
|
+
if (tenants.size === 0) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
await pool.query(
|
|
37
|
+
"DELETE FROM lattice_database_configs WHERE tenant_id = ANY($1::text[])",
|
|
38
|
+
[[...tenants]],
|
|
39
|
+
);
|
|
40
|
+
tenants.clear();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterAll(async () => {
|
|
44
|
+
await pool.end();
|
|
45
|
+
await store.dispose();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function tenantId(): string {
|
|
49
|
+
const id = `db-config-${randomUUID()}`;
|
|
50
|
+
tenants.add(id);
|
|
51
|
+
return id;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function config(overrides: Partial<DatabaseConfig> = {}): DatabaseConfig {
|
|
55
|
+
return {
|
|
56
|
+
type: "postgres",
|
|
57
|
+
host: "db.example.com",
|
|
58
|
+
port: 5432,
|
|
59
|
+
database: "app",
|
|
60
|
+
user: "app_user",
|
|
61
|
+
password: "super-secret",
|
|
62
|
+
ssl: true,
|
|
63
|
+
...overrides,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function createConfig(tenant: string) {
|
|
68
|
+
const id = randomUUID();
|
|
69
|
+
const key = `key-${randomUUID()}`;
|
|
70
|
+
const entry = await store.createConfig(tenant, id, {
|
|
71
|
+
key,
|
|
72
|
+
name: "Primary",
|
|
73
|
+
description: "Primary database",
|
|
74
|
+
config: config(),
|
|
75
|
+
});
|
|
76
|
+
return { id, key, entry };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
it("creates and reads a config by id and by key", async () => {
|
|
80
|
+
const tenant = tenantId();
|
|
81
|
+
const { id, key, entry } = await createConfig(tenant);
|
|
82
|
+
|
|
83
|
+
expect(entry).toMatchObject({
|
|
84
|
+
id,
|
|
85
|
+
tenantId: tenant,
|
|
86
|
+
key,
|
|
87
|
+
name: "Primary",
|
|
88
|
+
description: "Primary database",
|
|
89
|
+
});
|
|
90
|
+
expect(entry.config.database).toBe("app");
|
|
91
|
+
|
|
92
|
+
const byId = await store.getConfigById(tenant, id);
|
|
93
|
+
expect(byId?.id).toBe(id);
|
|
94
|
+
expect(byId?.config.host).toBe("db.example.com");
|
|
95
|
+
|
|
96
|
+
const byKey = await store.getConfigByKey(tenant, key);
|
|
97
|
+
expect(byKey?.id).toBe(id);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("encrypts the password at rest and decrypts it on read", async () => {
|
|
101
|
+
const tenant = tenantId();
|
|
102
|
+
const { id } = await createConfig(tenant);
|
|
103
|
+
|
|
104
|
+
const raw = await pool.query<{ config: { password: string } }>(
|
|
105
|
+
"SELECT config FROM lattice_database_configs WHERE tenant_id = $1 AND id = $2",
|
|
106
|
+
[tenant, id],
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(raw.rows[0].config.password).not.toBe("super-secret");
|
|
110
|
+
expect(decrypt(raw.rows[0].config.password)).toBe("super-secret");
|
|
111
|
+
|
|
112
|
+
const read = await store.getConfigById(tenant, id);
|
|
113
|
+
expect(read?.config.password).toBe("super-secret");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("lists a tenant's configs and includes them in the cross-tenant listing", async () => {
|
|
117
|
+
const tenant = tenantId();
|
|
118
|
+
const first = await createConfig(tenant);
|
|
119
|
+
const second = await createConfig(tenant);
|
|
120
|
+
|
|
121
|
+
const listed = await store.getAllConfigs(tenant);
|
|
122
|
+
expect(listed.map((entry) => entry.id).sort()).toEqual([first.id, second.id].sort());
|
|
123
|
+
|
|
124
|
+
const all = await store.getAllConfigsWithoutTenant();
|
|
125
|
+
const ids = all.map((entry) => entry.id);
|
|
126
|
+
expect(ids).toContain(first.id);
|
|
127
|
+
expect(ids).toContain(second.id);
|
|
128
|
+
expect(all.find((entry) => entry.id === first.id)?.config.password).toBe("super-secret");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("updates fields and re-encrypts a new password", async () => {
|
|
132
|
+
const tenant = tenantId();
|
|
133
|
+
const { id, key } = await createConfig(tenant);
|
|
134
|
+
|
|
135
|
+
const updated = await store.updateConfig(tenant, id, {
|
|
136
|
+
name: "Renamed",
|
|
137
|
+
description: "Updated description",
|
|
138
|
+
config: config({ host: "new.example.com", password: "rotated-secret" }),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(updated).toMatchObject({
|
|
142
|
+
id,
|
|
143
|
+
key,
|
|
144
|
+
name: "Renamed",
|
|
145
|
+
description: "Updated description",
|
|
146
|
+
});
|
|
147
|
+
expect(updated?.config.host).toBe("new.example.com");
|
|
148
|
+
expect(updated?.config.password).toBe("rotated-secret");
|
|
149
|
+
|
|
150
|
+
const raw = await pool.query<{ config: { password: string } }>(
|
|
151
|
+
"SELECT config FROM lattice_database_configs WHERE tenant_id = $1 AND id = $2",
|
|
152
|
+
[tenant, id],
|
|
153
|
+
);
|
|
154
|
+
expect(decrypt(raw.rows[0].config.password)).toBe("rotated-secret");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("keeps the encrypted password when a partial update omits config", async () => {
|
|
158
|
+
const tenant = tenantId();
|
|
159
|
+
const { id } = await createConfig(tenant);
|
|
160
|
+
|
|
161
|
+
const updated = await store.updateConfig(tenant, id, { name: "Only renamed" });
|
|
162
|
+
|
|
163
|
+
expect(updated?.name).toBe("Only renamed");
|
|
164
|
+
expect(updated?.config.password).toBe("super-secret");
|
|
165
|
+
|
|
166
|
+
const raw = await pool.query<{ config: { password: string } }>(
|
|
167
|
+
"SELECT config FROM lattice_database_configs WHERE tenant_id = $1 AND id = $2",
|
|
168
|
+
[tenant, id],
|
|
169
|
+
);
|
|
170
|
+
expect(raw.rows[0].config.password).not.toBe("super-secret");
|
|
171
|
+
expect(decrypt(raw.rows[0].config.password)).toBe("super-secret");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("reports has and deletes a config", async () => {
|
|
175
|
+
const tenant = tenantId();
|
|
176
|
+
const { id } = await createConfig(tenant);
|
|
177
|
+
|
|
178
|
+
expect(await store.hasConfig(tenant, id)).toBe(true);
|
|
179
|
+
expect(await store.deleteConfig(tenant, id)).toBe(true);
|
|
180
|
+
expect(await store.hasConfig(tenant, id)).toBe(false);
|
|
181
|
+
expect(await store.getConfigById(tenant, id)).toBeNull();
|
|
182
|
+
expect(await store.deleteConfig(tenant, id)).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("isolates configs by tenant", async () => {
|
|
186
|
+
const tenantA = tenantId();
|
|
187
|
+
const tenantB = tenantId();
|
|
188
|
+
const key = `shared-${randomUUID()}`;
|
|
189
|
+
const idA = randomUUID();
|
|
190
|
+
const idB = randomUUID();
|
|
191
|
+
|
|
192
|
+
await store.createConfig(tenantA, idA, { key, config: config() });
|
|
193
|
+
await store.createConfig(tenantB, idB, { key, config: config() });
|
|
194
|
+
|
|
195
|
+
expect(await store.getConfigById(tenantA, idB)).toBeNull();
|
|
196
|
+
expect((await store.getConfigByKey(tenantA, key))?.id).toBe(idA);
|
|
197
|
+
expect((await store.getAllConfigs(tenantA)).map((entry) => entry.id)).toEqual([idA]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("returns absent results for an unknown config", async () => {
|
|
201
|
+
const tenant = tenantId();
|
|
202
|
+
const missing = randomUUID();
|
|
203
|
+
|
|
204
|
+
expect(await store.getConfigById(tenant, missing)).toBeNull();
|
|
205
|
+
expect(await store.getConfigByKey(tenant, "missing")).toBeNull();
|
|
206
|
+
expect(await store.updateConfig(tenant, missing, { name: "x" })).toBeNull();
|
|
207
|
+
expect(await store.hasConfig(tenant, missing)).toBe(false);
|
|
208
|
+
expect(await store.deleteConfig(tenant, missing)).toBe(false);
|
|
209
|
+
});
|
|
210
|
+
});
|