Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 301d00f

Browse filesBrowse files
committed
feat: add optimistic concurrency support for admin api
1 parent 07b77b2 commit 301d00f
Copy full SHA for 301d00f

4 files changed

+76-1Lines changed: 76 additions & 1 deletion

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎packages/admin/src/types.ts‎

Copy file name to clipboardExpand all lines: packages/admin/src/types.ts
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ export interface UpdateConfigRequest {
259259
configName: string;
260260
description: string;
261261
editors: string[];
262+
baseVersion?: number;
262263
base: ConfigBase;
263264
variants: ConfigVariant[];
264265
}
Collapse file

‎packages/admin/tests/client.spec.ts‎

Copy file name to clipboardExpand all lines: packages/admin/tests/client.spec.ts
+28Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,34 @@ describe("Configs API", () => {
839839
expect.objectContaining({ method: "PUT" })
840840
);
841841
});
842+
843+
it("includes baseVersion in the request body when provided", async () => {
844+
mockFetch.mockResolvedValueOnce(jsonResponse({ id: "config-1", version: 3 }));
845+
846+
await admin.configs.update({
847+
projectId: "proj-1",
848+
configName: "my-config",
849+
description: "Updated",
850+
editors: [],
851+
baseVersion: 2,
852+
base: { value: false, schema: null, overrides: [] },
853+
variants: [],
854+
});
855+
856+
expect(mockFetch).toHaveBeenCalledWith(
857+
expect.stringContaining("/projects/proj-1/configs/my-config"),
858+
expect.objectContaining({
859+
method: "PUT",
860+
body: JSON.stringify({
861+
description: "Updated",
862+
editors: [],
863+
baseVersion: 2,
864+
base: { value: false, schema: null, overrides: [] },
865+
variants: [],
866+
}),
867+
})
868+
);
869+
});
842870
});
843871

844872
describe("delete", () => {
Collapse file

‎packages/test-suite/src/test-suite.ts‎

Copy file name to clipboardExpand all lines: packages/test-suite/src/test-suite.ts
+46-1Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*/
1010

1111
import { describe, it, beforeAll, afterAll, afterEach, expect } from "vitest";
12-
import { ReplaneAdmin, type ConfigValue, type Override } from "@replanejs/admin";
12+
import { ReplaneAdmin, ReplaneAdminError, type ConfigValue, type Override } from "@replanejs/admin";
1313
import { Replane, ReplaneError, ReplaneErrorCode } from "@replanejs/sdk";
1414
import type { ReplaneContext } from "@replanejs/sdk";
1515
import type { TestSuiteOptions, TestContext } from "./types";
@@ -165,6 +165,7 @@ function createTestContext(
165165
value: ConfigValue,
166166
configOptions?: {
167167
description?: string;
168+
baseVersion?: number;
168169
overrides?: Override[];
169170
}
170171
): Promise<void> {
@@ -173,6 +174,9 @@ function createTestContext(
173174
configName: name,
174175
description: configOptions?.description ?? "",
175176
editors: [],
177+
...(configOptions?.baseVersion !== undefined
178+
? { baseVersion: configOptions.baseVersion }
179+
: {}),
176180
base: {
177181
value,
178182
schema: null,
@@ -1599,6 +1603,47 @@ export function testSuite(options: TestSuiteOptions): void {
15991603
});
16001604
});
16011605

1606+
describe("Admin Config Versioning", () => {
1607+
it("should update a config when baseVersion matches", async () => {
1608+
const configName = uniqueId("base-version-config");
1609+
1610+
await ctx.createConfig(configName, "initial");
1611+
1612+
const config = await admin.configs.get({ projectId, configName });
1613+
1614+
await ctx.updateConfig(configName, "updated", {
1615+
baseVersion: config.version,
1616+
});
1617+
1618+
const client = trackClient(await ctx.createClient<Record<string, string>>({}));
1619+
expect(client.get(configName)).toBe("updated");
1620+
client.disconnect();
1621+
});
1622+
1623+
it("should reject stale baseVersion updates", async () => {
1624+
const configName = uniqueId("stale-base-version-config");
1625+
1626+
await ctx.createConfig(configName, "initial");
1627+
1628+
const config = await admin.configs.get({ projectId, configName });
1629+
1630+
await ctx.updateConfig(configName, "fresh", {
1631+
baseVersion: config.version,
1632+
});
1633+
1634+
await expect(
1635+
ctx.updateConfig(configName, "stale", {
1636+
baseVersion: config.version,
1637+
})
1638+
).rejects.toSatisfy(
1639+
(error: unknown) =>
1640+
error instanceof ReplaneAdminError &&
1641+
error.status === 400 &&
1642+
error.message.includes("Config was edited by another user")
1643+
);
1644+
});
1645+
});
1646+
16021647
// ==================== CONCURRENT CLIENTS TESTS ====================
16031648

16041649
describe("Concurrent Clients", () => {
Collapse file

‎packages/test-suite/src/types.ts‎

Copy file name to clipboardExpand all lines: packages/test-suite/src/types.ts
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export interface TestContext {
7171
value: ConfigValue,
7272
options?: {
7373
description?: string;
74+
baseVersion?: number;
7475
overrides?: Override[];
7576
}
7677
): Promise<void>;

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.