Files
jerapah-flow/packages/server/test/config-refs-smoke.js
T
nsrb 6c13e45213 feat(variables): introduce variable management system
- Added a new module for managing variables, including creation, retrieval, updating, and deletion functionalities.
- Implemented a new API endpoint for variable operations, allowing users to interact with variables through HTTP requests.
- Enhanced the configuration reference system to support variable references, replacing the previous KV reference system.
- Updated existing workflows and scripts to utilize the new variable management features, improving flexibility in configuration handling.
- Introduced smoke tests to validate the functionality of the variable system, ensuring reliability and robustness.

This update significantly enhances the ability to manage dynamic variables within workflows, improving overall usability and maintainability.
2026-08-16 22:23:49 +07:00

214 lines
7.1 KiB
JavaScript

import { migrate, db } from "../db.js";
import { upsertSecret, deleteSecret } from "../secrets-store.js";
import { deleteVariable, upsertVariable } from "../variables-store.js";
import { Secret } from "../secret-value.js";
import { parseConfigRef, resolveConfigRefs } from "../config-refs.js";
await migrate();
function assert(cond, msg) {
if (!cond) throw new Error(msg);
}
async function assertRejects(fn, match) {
try {
await fn();
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
if (match && !message.includes(match)) {
throw new Error(`rejected with "${message}", expected to include "${match}"`);
}
return;
}
throw new Error(`expected to reject (${match ?? "any error"})`);
}
const owner = "default";
const workflowKey = "default/config-refs-smoke.yaml";
const ctx = { owner, workflowKey, context: {} };
function assertParse(value, expected) {
const got = parseConfigRef(value);
if (expected == null) {
assert(got == null, `expected null parse for ${JSON.stringify(value)}, got ${JSON.stringify(got)}`);
return;
}
assert(got != null, `expected parse for ${JSON.stringify(value)}`);
assert(got.kind === expected.kind, `kind ${got.kind} !== ${expected.kind}`);
assert(got.name === expected.name, `name ${JSON.stringify(got.name)} !== ${JSON.stringify(expected.name)}`);
}
assertParse("password123", null);
assertParse("$FOO_bar", null);
assertParse("$SECRET", null);
assertParse(" password123 ", null);
assertParse("$SECRET_zte_modem_password", { kind: "secret", name: "zte_modem_password" });
assertParse(" $SECRET_zte_modem_password ", { kind: "secret", name: "zte_modem_password" });
assertParse("Bearer $SECRET_x", null);
assertParse("$KV_modem password", null);
assertParse("$VAR_ntfy_url", { kind: "var", name: "ntfy_url" });
assertParse("$CONTEXT_token", { kind: "context", name: "token" });
assertParse("$SECRET_", { kind: "secret", name: "" });
assertParse("$CONTEXT_SECRET_foo", { kind: "context", name: "SECRET_foo" });
{
const literal = await resolveConfigRefs("password123", ctx);
assert(literal === "password123", "literal passthrough");
const unknown = await resolveConfigRefs("$FOO_bar", ctx);
assert(unknown === "$FOO_bar", "$FOO_bar stays literal");
const kvLiteral = await resolveConfigRefs("$KV_modem_password", ctx);
assert(kvLiteral === "$KV_modem_password", "$KV_ stays literal");
const embedded = await resolveConfigRefs("Bearer $SECRET_x", ctx);
assert(embedded === "Bearer $SECRET_x", "mid-string stays literal");
const number = await resolveConfigRefs(42, ctx);
assert(number === 42, "number passthrough");
}
const secret = await upsertSecret({
owner,
name: "config_refs_smoke_token",
value: "s3cret-ok",
});
const varUrl = await upsertVariable({
owner,
name: "config_refs_smoke_url",
type: "string",
value: "https://example.test",
});
const varRetry = await upsertVariable({
owner,
name: "config_refs_smoke_retry",
type: "number",
value: 3,
});
const varDebug = await upsertVariable({
owner,
name: "config_refs_smoke_debug",
type: "boolean",
value: false,
});
try {
{
const resolved = await resolveConfigRefs("$SECRET_config_refs_smoke_token", ctx);
assert(resolved === "s3cret-ok", "secret resolve");
}
{
const resolved = await resolveConfigRefs(" $SECRET_config_refs_smoke_token ", ctx);
assert(resolved === "s3cret-ok", "secret resolve trimmed");
}
{
const resolved = await resolveConfigRefs("$VAR_config_refs_smoke_url", ctx);
assert(resolved === "https://example.test", "var string");
}
{
const resolved = await resolveConfigRefs("$VAR_config_refs_smoke_retry", ctx);
assert(resolved === 3, "var number stays number");
assert(typeof resolved === "number", "var number type");
}
{
const resolved = await resolveConfigRefs("$VAR_config_refs_smoke_debug", ctx);
assert(resolved === false, "var boolean stays false");
assert(typeof resolved === "boolean", "var boolean type");
}
{
const resolved = await resolveConfigRefs("$CONTEXT_token", {
...ctx,
context: { token: "ctx-token-ok" },
});
assert(resolved === "ctx-token-ok", "context string");
}
{
const resolved = await resolveConfigRefs("$CONTEXT_n", {
...ctx,
context: { n: 7 },
});
assert(resolved === "7", "context number stringify");
}
{
const wrapped = new Secret("wrapped-secret-ok");
const resolved = await resolveConfigRefs("$CONTEXT_tok", {
...ctx,
context: { tok: wrapped },
});
assert(resolved === "wrapped-secret-ok", "context Secret unwrap");
}
{
const nested = await resolveConfigRefs(
{
url: "$VAR_config_refs_smoke_url",
retry: "$VAR_config_refs_smoke_retry",
debug: "$VAR_config_refs_smoke_debug",
password: "$SECRET_config_refs_smoke_token",
headers: { Authorization: "$KV_modem_password" },
extra: ["$CONTEXT_token", "plain"],
},
{ ...ctx, context: { token: "ctx-token-ok" } },
);
assert(nested.url === "https://example.test", "nested var string");
assert(nested.retry === 3, "nested var number");
assert(nested.debug === false, "nested var boolean");
assert(nested.password === "s3cret-ok", "nested secret");
assert(nested.headers.Authorization === "$KV_modem_password", "nested $KV_ stays literal");
assert(nested.extra[0] === "ctx-token-ok", "nested array context");
assert(nested.extra[1] === "plain", "nested array literal");
}
const data = { password: "$SECRET_config_refs_smoke_token" };
const config = { password: "$SECRET_config_refs_smoke_token" };
const resolvedConfig = await resolveConfigRefs(config, ctx);
assert(resolvedConfig.password === "s3cret-ok", "config resolved");
assert(data.password === "$SECRET_config_refs_smoke_token", "data not walked");
assert(config.password === "$SECRET_config_refs_smoke_token", "input config not mutated");
await assertRejects(
() => resolveConfigRefs("$SECRET_does_not_exist_xyz", ctx),
'secret "does_not_exist_xyz" not found',
);
await assertRejects(
() => resolveConfigRefs("$SECRET_not valid", ctx),
"invalid secret name",
);
await assertRejects(
() => resolveConfigRefs("$SECRET_", ctx),
"invalid secret name",
);
await assertRejects(
() => resolveConfigRefs("$CONTEXT_missing", ctx),
'context "missing" not found',
);
await assertRejects(
() =>
resolveConfigRefs("$CONTEXT_obj", {
...ctx,
context: { obj: { a: 1 } },
}),
'context "obj" is not a scalar',
);
await assertRejects(
() => resolveConfigRefs("$CONTEXT_", ctx),
"empty context key",
);
await assertRejects(
() => resolveConfigRefs("$VAR_does_not_exist_xyz", ctx),
'variable "does_not_exist_xyz" not found',
);
await assertRejects(
() => resolveConfigRefs("$VAR_not valid", ctx),
"invalid variable name",
);
await assertRejects(
() => resolveConfigRefs("$VAR_", ctx),
"empty variable name",
);
} finally {
await deleteSecret(secret.id);
await deleteVariable(varUrl.id);
await deleteVariable(varRetry.id);
await deleteVariable(varDebug.id);
}
console.log("config-refs smoke test passed");
await db.destroy();