Skip to content

Commit 1be7e32

Browse files
saurawwSaurav Suman
andauthored
test: added tests for secrets (#861)
Co-authored-by: Saurav Suman <saurav.suman@Saurav-Suman-DQGRVM2TW4.local>
1 parent 190f636 commit 1be7e32

File tree

3 files changed

+366
-1
lines changed

3 files changed

+366
-1
lines changed

makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,8 @@ test: WASM_PACK_MODE=--profiling
235235
test: setup frontend superposition
236236
cargo test
237237
@echo "Running superposition"
238-
$(MAKE) run &
238+
MASTER_ENCRYPTION_KEY=$${MASTER_ENCRYPTION_KEY:-"dGVzdC1tYXN0ZXIta2V5LTMyLWNoYXJhY3RlcnMtb2s="} \
239+
$(MAKE) run &
239240
@echo "Awaiting superposition boot..."
240241
## FIXME Curl doesn't retry.
241242
@curl --silent --retry 10 \

tests/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,30 @@ import {
44
ListOrganisationCommand,
55
ListWorkspaceCommand,
66
WorkspaceStatus,
7+
MigrateWorkspaceSchemaCommand
78
} from "@juspay/superposition-sdk";
89
import { ENV, superpositionClient } from "./env.ts";
910

1011
const TEST_ORG_NAME = "testorg";
1112
const TEST_WORKSPACE = "testworkspace";
1213

14+
async function setupWorkspaceEncryption(workspaceName: string) {
15+
console.log(`Setting workspace encryption key for workspace name: ${workspaceName}`);
16+
try {
17+
const migrateCommand = new MigrateWorkspaceSchemaCommand({
18+
org_id: ENV.org_id,
19+
workspace_name: workspaceName
20+
});
21+
22+
const response = await superpositionClient.send(migrateCommand);
23+
console.log("Workspace migration/encryption setup completed successfully");
24+
return response;
25+
} catch (err: any) {
26+
console.log("Failed to setup workspace encryption:", err.message);
27+
throw err;
28+
}
29+
}
30+
1331
async function setupWorkspace() {
1432
console.log("Setting up test workspace");
1533
const listWorkspaceCommand = new ListWorkspaceCommand({
@@ -42,6 +60,8 @@ async function setupWorkspace() {
4260
);
4361
}
4462

63+
await setupWorkspaceEncryption(TEST_WORKSPACE);
64+
4565
ENV.workspace_id = TEST_WORKSPACE;
4666
}
4767

tests/src/secrets.test.ts

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
import {
2+
CreateSecretCommand,
3+
GetSecretCommand,
4+
CreateFunctionCommand,
5+
DeleteFunctionCommand,
6+
UpdateSecretCommand,
7+
DeleteSecretCommand,
8+
TestCommand,
9+
FunctionTypes,
10+
FunctionRuntimeVersion,
11+
} from "@juspay/superposition-sdk";
12+
import { expect, describe, test, afterAll } from "bun:test";
13+
import { ENV, superpositionClient } from "../env.ts";
14+
15+
describe("Secret Operations", () => {
16+
const createdSecrets: Set<string> = new Set();
17+
const createdFunctions: Set<string> = new Set();
18+
19+
function trackSecret(name: string) {
20+
createdSecrets.add(name);
21+
}
22+
23+
function trackFunction(name: string) {
24+
createdFunctions.add(name);
25+
}
26+
27+
afterAll(async () => {
28+
console.log(`Cleaning up ${createdSecrets.size} secrets...`);
29+
30+
for (const secretName of createdSecrets) {
31+
try {
32+
const deleteCommand = new DeleteSecretCommand({
33+
workspace_id: ENV.workspace_id,
34+
org_id: ENV.org_id,
35+
name: secretName,
36+
});
37+
await superpositionClient.send(deleteCommand);
38+
console.log(`Deleted secret: ${secretName}`);
39+
} catch (error: any) {
40+
console.error(
41+
`Failed to delete secret ${secretName}:`,
42+
error.message
43+
);
44+
}
45+
}
46+
47+
console.log(`Cleaning up ${createdFunctions.size} functions...`);
48+
49+
for (const funcName of createdFunctions) {
50+
try {
51+
const deleteCommand = new DeleteFunctionCommand({
52+
workspace_id: ENV.workspace_id,
53+
org_id: ENV.org_id,
54+
function_name: funcName,
55+
});
56+
await superpositionClient.send(deleteCommand);
57+
console.log(`Deleted function: ${funcName}`);
58+
} catch (error: any) {
59+
console.error(
60+
`Failed to delete function ${funcName}:`,
61+
error.message
62+
);
63+
}
64+
}
65+
});
66+
67+
test("Create secret", async () => {
68+
const secretName = `TEST_API_KEY_CREATE`;
69+
const createCommand = new CreateSecretCommand({
70+
workspace_id: ENV.workspace_id,
71+
org_id: ENV.org_id,
72+
name: secretName,
73+
value: "test-key-12345",
74+
description: "Test API key secret",
75+
change_reason: "Initial creation for testing",
76+
});
77+
78+
try {
79+
const response = await superpositionClient.send(createCommand);
80+
81+
trackSecret(response.name!);
82+
83+
expect(response.name).toBe(secretName);
84+
expect(response.description).toBe("Test API key secret");
85+
expect(response.created_by).toBeDefined();
86+
expect(response.created_at).toBeDefined();
87+
} catch (error: any) {
88+
console.log("Error creating secret:", error.message);
89+
throw error;
90+
}
91+
});
92+
93+
test("Get secret by Name", async () => {
94+
95+
try {
96+
let secretName = "TEST_API_KEY_CREATE";
97+
98+
const getCommand = new GetSecretCommand({
99+
org_id: ENV.org_id,
100+
workspace_id: ENV.workspace_id,
101+
name: secretName,
102+
});
103+
104+
const getResponse = await superpositionClient.send(getCommand);
105+
106+
expect(getResponse.name).toBe(secretName);
107+
expect(getResponse.value).toBeUndefined();
108+
expect(getResponse.description).toBe("Test API key secret");
109+
} catch (error: any) {
110+
console.log("Error getting secret:", error.message);
111+
throw error;
112+
}
113+
});
114+
115+
test("Verify secret value update via Function", async () => {
116+
const secretName = `UPDATE_VERIFY_${Date.now()}`;
117+
const functionName = `verify_update_${Date.now()}`;
118+
const originalValue = "original-secret-value";
119+
const updatedValue = "updated-secret-value";
120+
121+
const createCmd = new CreateSecretCommand({
122+
workspace_id: ENV.workspace_id,
123+
org_id: ENV.org_id,
124+
name: secretName,
125+
value: originalValue,
126+
description: "Testing update verification",
127+
change_reason: "Initial creation",
128+
});
129+
130+
const updateCmd = new UpdateSecretCommand({
131+
workspace_id: ENV.workspace_id,
132+
org_id: ENV.org_id,
133+
name: secretName,
134+
value: updatedValue,
135+
change_reason: "Updating for verification",
136+
});
137+
138+
const functionCode = `
139+
async function execute(payload) {
140+
return [SECRETS.${secretName}];
141+
}`;
142+
143+
const createFuncCmd = new CreateFunctionCommand({
144+
workspace_id: ENV.workspace_id,
145+
org_id: ENV.org_id,
146+
function_name: functionName,
147+
function: functionCode,
148+
description: "Verify secret update",
149+
change_reason: "Testing",
150+
runtime_version: FunctionRuntimeVersion.V1,
151+
function_type: FunctionTypes.VALUE_COMPUTE,
152+
});
153+
154+
const testCmd = new TestCommand({
155+
workspace_id: ENV.workspace_id,
156+
org_id: ENV.org_id,
157+
function_name: functionName,
158+
stage: "draft",
159+
request: {
160+
value_compute: {
161+
name: "",
162+
prefix: "",
163+
type: "ConfigKey",
164+
environment: { context: {}, overrides: {} },
165+
},
166+
},
167+
});
168+
169+
try {
170+
await superpositionClient.send(createCmd);
171+
trackSecret(secretName);
172+
await superpositionClient.send(createFuncCmd);
173+
trackFunction(functionName);
174+
175+
const testResponse1 = await superpositionClient.send(testCmd);
176+
expect(testResponse1.fn_output).toEqual([originalValue]);
177+
178+
await superpositionClient.send(updateCmd);
179+
180+
const testResponse2 = await superpositionClient.send(testCmd);
181+
expect(testResponse2.fn_output).toEqual([updatedValue]);
182+
} catch (error: any) {
183+
console.log("Error verifying secret update:", error.message);
184+
throw error;
185+
}
186+
});
187+
188+
test("Delete secret", async () => {
189+
const createCommand = new CreateSecretCommand({
190+
workspace_id: ENV.workspace_id,
191+
org_id: ENV.org_id,
192+
name: `DELETE_TEST_${Date.now()}`,
193+
value: "test-value",
194+
description: "creating for delete test",
195+
change_reason: "Creating for delete test",
196+
});
197+
198+
try {
199+
const createResponse = await superpositionClient.send(
200+
createCommand
201+
);
202+
const secretName = createResponse.name!;
203+
204+
const deleteCommand = new DeleteSecretCommand({
205+
workspace_id: ENV.workspace_id,
206+
org_id: ENV.org_id,
207+
name: secretName,
208+
});
209+
210+
await superpositionClient.send(deleteCommand);
211+
212+
// Verify deletion
213+
const getCommand = new GetSecretCommand({
214+
org_id: ENV.org_id,
215+
workspace_id: ENV.workspace_id,
216+
name: secretName,
217+
});
218+
219+
expect(superpositionClient.send(getCommand)).rejects.toThrow(
220+
"No records found"
221+
);
222+
} catch (error: any) {
223+
console.log("Error testing delete:", error.message);
224+
throw error;
225+
}
226+
});
227+
228+
test("Fail on Duplicate secret Name", async () => {
229+
const name = `DUPLICATE_TEST_${Date.now()}`;
230+
231+
const createCommand = new CreateSecretCommand({
232+
workspace_id: ENV.workspace_id,
233+
org_id: ENV.org_id,
234+
name: name,
235+
value: "test-value",
236+
description: "creating duplicate test",
237+
change_reason: "Creating for duplicate test",
238+
});
239+
240+
try {
241+
const createResponse = await superpositionClient.send(
242+
createCommand
243+
);
244+
trackSecret(createResponse.name!);
245+
246+
// Try to create duplicate
247+
const duplicateCommand = new CreateSecretCommand({
248+
workspace_id: ENV.workspace_id,
249+
org_id: ENV.org_id,
250+
name: name,
251+
value: "different-value",
252+
description: "Testing duplicate",
253+
change_reason: "Testing duplicate",
254+
});
255+
256+
expect(superpositionClient.send(duplicateCommand)).rejects.toThrow(
257+
"duplicate key value violates unique constraint"
258+
);
259+
} catch (error: any) {
260+
console.log("Error testing duplicate prevention:", error.message);
261+
throw error;
262+
}
263+
});
264+
265+
test("Fail on Get Non-Existent secret", async () => {
266+
const getCommand = new GetSecretCommand({
267+
org_id: ENV.org_id,
268+
workspace_id: ENV.workspace_id,
269+
name: "NON_EXISTENT_secret",
270+
});
271+
272+
expect(superpositionClient.send(getCommand)).rejects.toThrow(
273+
"No records found"
274+
);
275+
});
276+
277+
test("Fail on Update Non-Existent secret", async () => {
278+
const updateCommand = new UpdateSecretCommand({
279+
workspace_id: ENV.workspace_id,
280+
org_id: ENV.org_id,
281+
name: "NON_EXISTENT_secret",
282+
value: "new-value",
283+
change_reason: "Testing update of non-existent secret",
284+
});
285+
286+
expect(superpositionClient.send(updateCommand)).rejects.toThrow(
287+
"No records found"
288+
);
289+
});
290+
291+
test("Fail on Delete Non-Existent secret", async () => {
292+
const deleteCommand = new DeleteSecretCommand({
293+
workspace_id: ENV.workspace_id,
294+
org_id: ENV.org_id,
295+
name: "NON_EXISTENT_secret",
296+
});
297+
298+
expect(superpositionClient.send(deleteCommand)).rejects.toThrow(
299+
"No records found"
300+
);
301+
});
302+
303+
test("Fail on Empty secret Name", async () => {
304+
const command = new CreateSecretCommand({
305+
workspace_id: ENV.workspace_id,
306+
org_id: ENV.org_id,
307+
name: "",
308+
value: "test-value",
309+
description: "Testing empty name validation",
310+
change_reason: "Testing empty name validation",
311+
});
312+
313+
expect(superpositionClient.send(command)).rejects.toThrow(
314+
"Parse error"
315+
);
316+
});
317+
318+
test("Fail on Invalid secret Name Pattern", async () => {
319+
const invalidNames = [
320+
"invalid-name-with-dashes", // dashes not allowed
321+
"invalid name with spaces", // spaces not allowed
322+
"invalid.name.with.dots", // dots not allowed
323+
"123_STARTS_WITH_NUMBER", // starts with number
324+
"special@chars#not$allowed", // special characters
325+
"lowercase_not_allowed", // lowercase not allowed
326+
"Mixed_Case_Name", // mixed case not allowed
327+
];
328+
329+
for (const invalidName of invalidNames) {
330+
const command = new CreateSecretCommand({
331+
workspace_id: ENV.workspace_id,
332+
org_id: ENV.org_id,
333+
name: invalidName,
334+
value: "test-value",
335+
description: `Testing invalid name: ${invalidName}`,
336+
change_reason: "Testing invalid name pattern",
337+
});
338+
339+
expect(superpositionClient.send(command)).rejects.toThrow(
340+
"Parse error"
341+
);
342+
}
343+
});
344+
});

0 commit comments

Comments
 (0)