@desplega.ai/qa-use 2.15.3 → 2.16.0
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/dist/lib/api/index.d.ts +11 -0
- package/dist/lib/api/index.d.ts.map +1 -1
- package/dist/lib/api/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/src/cli/commands/test/diff.d.ts.map +1 -1
- package/dist/src/cli/commands/test/diff.js +50 -2
- package/dist/src/cli/commands/test/diff.js.map +1 -1
- package/dist/src/cli/commands/test/index.d.ts.map +1 -1
- package/dist/src/cli/commands/test/index.js +2 -0
- package/dist/src/cli/commands/test/index.js.map +1 -1
- package/dist/src/cli/commands/test/info.d.ts.map +1 -1
- package/dist/src/cli/commands/test/info.js +52 -7
- package/dist/src/cli/commands/test/info.js.map +1 -1
- package/dist/src/cli/commands/test/run.d.ts.map +1 -1
- package/dist/src/cli/commands/test/run.js +27 -1
- package/dist/src/cli/commands/test/run.js.map +1 -1
- package/dist/src/cli/commands/test/vars/index.d.ts +10 -0
- package/dist/src/cli/commands/test/vars/index.d.ts.map +1 -0
- package/dist/src/cli/commands/test/vars/index.js +16 -0
- package/dist/src/cli/commands/test/vars/index.js.map +1 -0
- package/dist/src/cli/commands/test/vars/list.d.ts +10 -0
- package/dist/src/cli/commands/test/vars/list.d.ts.map +1 -0
- package/dist/src/cli/commands/test/vars/list.js +96 -0
- package/dist/src/cli/commands/test/vars/list.js.map +1 -0
- package/dist/src/cli/commands/test/vars/set.d.ts +31 -0
- package/dist/src/cli/commands/test/vars/set.d.ts.map +1 -0
- package/dist/src/cli/commands/test/vars/set.js +249 -0
- package/dist/src/cli/commands/test/vars/set.js.map +1 -0
- package/dist/src/cli/commands/test/vars/unset.d.ts +12 -0
- package/dist/src/cli/commands/test/vars/unset.d.ts.map +1 -0
- package/dist/src/cli/commands/test/vars/unset.js +86 -0
- package/dist/src/cli/commands/test/vars/unset.js.map +1 -0
- package/dist/src/cli/generated/docs-content.d.ts +1 -1
- package/dist/src/cli/generated/docs-content.d.ts.map +1 -1
- package/dist/src/cli/generated/docs-content.js +75 -1
- package/dist/src/cli/generated/docs-content.js.map +1 -1
- package/dist/src/cli/lib/output.d.ts.map +1 -1
- package/dist/src/cli/lib/output.js +14 -1
- package/dist/src/cli/lib/output.js.map +1 -1
- package/dist/src/cli/lib/test-vars.d.ts +90 -0
- package/dist/src/cli/lib/test-vars.d.ts.map +1 -0
- package/dist/src/cli/lib/test-vars.js +167 -0
- package/dist/src/cli/lib/test-vars.js.map +1 -0
- package/dist/src/cli/lib/uuid.d.ts +9 -0
- package/dist/src/cli/lib/uuid.d.ts.map +1 -0
- package/dist/src/cli/lib/uuid.js +11 -0
- package/dist/src/cli/lib/uuid.js.map +1 -0
- package/dist/src/server.js +1 -1
- package/dist/src/server.js.map +1 -1
- package/dist/src/types/test-definition.d.ts +123 -20
- package/dist/src/types/test-definition.d.ts.map +1 -1
- package/dist/src/types/test-definition.js +2 -2
- package/lib/api/index.ts +11 -0
- package/package.json +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qa-use test vars - Manage typed variables on a test.
|
|
3
|
+
*
|
|
4
|
+
* Subgroup nested under `qa-use test`. Local YAML files are the primary
|
|
5
|
+
* surface; `--id <uuid>` falls back to read-modify-write against the backend
|
|
6
|
+
* via `client.exportTest` + `client.importTestDefinition`.
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import { listCommand } from './list.js';
|
|
10
|
+
import { setCommand } from './set.js';
|
|
11
|
+
import { unsetCommand } from './unset.js';
|
|
12
|
+
export const varsCommand = new Command('vars').description('Manage typed variables on a test (list/set/unset)');
|
|
13
|
+
varsCommand.addCommand(listCommand);
|
|
14
|
+
varsCommand.addCommand(setCommand);
|
|
15
|
+
varsCommand.addCommand(unsetCommand);
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../src/cli/commands/test/vars/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,WAAW,CACxD,mDAAmD,CACpD,CAAC;AAEF,WAAW,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AACpC,WAAW,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;AACnC,WAAW,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qa-use test vars list - List typed variables on a test.
|
|
3
|
+
*
|
|
4
|
+
* Local-file path (`<file>` positional) is the primary surface; `--id <uuid>`
|
|
5
|
+
* exports the YAML from the backend in-memory and runs the same rendering
|
|
6
|
+
* path. Mutual exclusion is enforced via `resolveVarsTarget`.
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
export declare const listCommand: Command;
|
|
10
|
+
//# sourceMappingURL=list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../../../../src/cli/commands/test/vars/list.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkEpC,eAAO,MAAM,WAAW,SAiDpB,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qa-use test vars list - List typed variables on a test.
|
|
3
|
+
*
|
|
4
|
+
* Local-file path (`<file>` positional) is the primary surface; `--id <uuid>`
|
|
5
|
+
* exports the YAML from the backend in-memory and runs the same rendering
|
|
6
|
+
* path. Mutual exclusion is enforced via `resolveVarsTarget`.
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import * as yaml from 'yaml';
|
|
10
|
+
import { createApiClient, loadConfig } from '../../../lib/config.js';
|
|
11
|
+
import { error, formatError } from '../../../lib/output.js';
|
|
12
|
+
import { printTable } from '../../../lib/table.js';
|
|
13
|
+
import { getNormalizedEntry, maskValue, readVarsFromYamlFile, resolveVarsTarget, } from '../../../lib/test-vars.js';
|
|
14
|
+
function buildRow(key, raw) {
|
|
15
|
+
const entry = getNormalizedEntry(raw);
|
|
16
|
+
return {
|
|
17
|
+
key,
|
|
18
|
+
type: entry.type ?? 'custom',
|
|
19
|
+
lifetime: entry.lifetime ?? 'test',
|
|
20
|
+
context: entry.context ?? 'test',
|
|
21
|
+
is_sensitive: entry.is_sensitive ?? false,
|
|
22
|
+
value: maskValue(raw),
|
|
23
|
+
rawValue: entry.value ?? undefined,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function rowsFromVariables(vars) {
|
|
27
|
+
return Object.entries(vars).map(([k, v]) => buildRow(k, v));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build the JSON payload emitted by `--json`.
|
|
31
|
+
* Sensitive entries omit the `value` key entirely so consumers rely on
|
|
32
|
+
* `is_sensitive: true` as the redaction signal.
|
|
33
|
+
*/
|
|
34
|
+
function rowsForJson(rows) {
|
|
35
|
+
return rows.map((row) => {
|
|
36
|
+
const out = {
|
|
37
|
+
key: row.key,
|
|
38
|
+
type: row.type,
|
|
39
|
+
lifetime: row.lifetime,
|
|
40
|
+
context: row.context,
|
|
41
|
+
is_sensitive: row.is_sensitive,
|
|
42
|
+
};
|
|
43
|
+
if (!row.is_sensitive && row.rawValue !== undefined) {
|
|
44
|
+
out.value = row.rawValue;
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export const listCommand = new Command('list')
|
|
50
|
+
.description('List typed variables on a test (local YAML file or remote --id)')
|
|
51
|
+
.argument('[file]', 'Path to a local test YAML file')
|
|
52
|
+
.option('--id <uuid>', 'Remote test UUID — exports YAML and lists from there')
|
|
53
|
+
.option('--json', 'Output as JSON (sensitive values redacted)')
|
|
54
|
+
.action(async (file, options) => {
|
|
55
|
+
try {
|
|
56
|
+
const target = resolveVarsTarget({ file, id: options.id });
|
|
57
|
+
let vars;
|
|
58
|
+
if (target.kind === 'file') {
|
|
59
|
+
({ vars } = await readVarsFromYamlFile(target.path));
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const config = await loadConfig();
|
|
63
|
+
const client = createApiClient(config);
|
|
64
|
+
const yamlText = await client.exportTest(target.uuid, 'yaml', false);
|
|
65
|
+
const def = yaml.parse(yamlText);
|
|
66
|
+
vars = def?.variables ?? {};
|
|
67
|
+
}
|
|
68
|
+
const rows = rowsFromVariables(vars);
|
|
69
|
+
if (options.json) {
|
|
70
|
+
console.log(JSON.stringify(rowsForJson(rows), null, 2));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const columns = [
|
|
74
|
+
{ key: 'key', header: 'KEY' },
|
|
75
|
+
{ key: 'type', header: 'TYPE' },
|
|
76
|
+
{ key: 'lifetime', header: 'LIFETIME' },
|
|
77
|
+
{ key: 'context', header: 'CONTEXT' },
|
|
78
|
+
{
|
|
79
|
+
key: 'is_sensitive',
|
|
80
|
+
header: 'SENSITIVE',
|
|
81
|
+
format: (v) => (v ? 'yes' : 'no'),
|
|
82
|
+
},
|
|
83
|
+
{ key: 'value', header: 'VALUE' },
|
|
84
|
+
];
|
|
85
|
+
printTable(columns, rows, {
|
|
86
|
+
emptyMessage: target.kind === 'file'
|
|
87
|
+
? `No variables defined in ${target.path}`
|
|
88
|
+
: `No variables on test ${target.uuid}`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
console.log(error(`Failed to list variables: ${formatError(err)}`));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../../../../../src/cli/commands/test/vars/list.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qa-use test vars set - Set a typed variable on a test.
|
|
3
|
+
*
|
|
4
|
+
* Local-file path: mutates `<file>` in place via yaml's Document API so
|
|
5
|
+
* comments and key order survive a round-trip.
|
|
6
|
+
*
|
|
7
|
+
* Remote `--id` path: exports YAML, parses to a `TestDefinition`, mutates
|
|
8
|
+
* the parsed object directly, and re-imports via `client.importTestDefinition`.
|
|
9
|
+
* Comment/key-order preservation is meaningless across export → import — the
|
|
10
|
+
* server normalizes formatting — so the Document API path is **not** used
|
|
11
|
+
* remotely on purpose.
|
|
12
|
+
*
|
|
13
|
+
* Validation:
|
|
14
|
+
* --type / --lifetime / --context must match the runtime arrays in
|
|
15
|
+
* src/cli/lib/test-vars.ts (which mirror the auto-generated unions).
|
|
16
|
+
*
|
|
17
|
+
* Mutation rules (apply identically to both paths):
|
|
18
|
+
* - --key + --value only → simple form (`key: value`)
|
|
19
|
+
* - any of --type/--lifetime/... → full form ({value, type, ...})
|
|
20
|
+
* - existing full form stays full form even if only --value is passed
|
|
21
|
+
*
|
|
22
|
+
* Sensitive-preserve:
|
|
23
|
+
* - Existing sensitive var, `--sensitive` without `--value` → keep value.
|
|
24
|
+
* Locally we read from the file. Remotely we rely on cope's import-merge
|
|
25
|
+
* rule which keeps the stored DB value when is_sensitive=true and no
|
|
26
|
+
* value is supplied.
|
|
27
|
+
* - New key with `--sensitive` and no `--value` → exit 1.
|
|
28
|
+
*/
|
|
29
|
+
import { Command } from 'commander';
|
|
30
|
+
export declare const setCommand: Command;
|
|
31
|
+
//# sourceMappingURL=set.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"set.d.ts","sourceRoot":"","sources":["../../../../../../src/cli/commands/test/vars/set.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgNpC,eAAO,MAAM,UAAU,SA0EnB,CAAC"}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qa-use test vars set - Set a typed variable on a test.
|
|
3
|
+
*
|
|
4
|
+
* Local-file path: mutates `<file>` in place via yaml's Document API so
|
|
5
|
+
* comments and key order survive a round-trip.
|
|
6
|
+
*
|
|
7
|
+
* Remote `--id` path: exports YAML, parses to a `TestDefinition`, mutates
|
|
8
|
+
* the parsed object directly, and re-imports via `client.importTestDefinition`.
|
|
9
|
+
* Comment/key-order preservation is meaningless across export → import — the
|
|
10
|
+
* server normalizes formatting — so the Document API path is **not** used
|
|
11
|
+
* remotely on purpose.
|
|
12
|
+
*
|
|
13
|
+
* Validation:
|
|
14
|
+
* --type / --lifetime / --context must match the runtime arrays in
|
|
15
|
+
* src/cli/lib/test-vars.ts (which mirror the auto-generated unions).
|
|
16
|
+
*
|
|
17
|
+
* Mutation rules (apply identically to both paths):
|
|
18
|
+
* - --key + --value only → simple form (`key: value`)
|
|
19
|
+
* - any of --type/--lifetime/... → full form ({value, type, ...})
|
|
20
|
+
* - existing full form stays full form even if only --value is passed
|
|
21
|
+
*
|
|
22
|
+
* Sensitive-preserve:
|
|
23
|
+
* - Existing sensitive var, `--sensitive` without `--value` → keep value.
|
|
24
|
+
* Locally we read from the file. Remotely we rely on cope's import-merge
|
|
25
|
+
* rule which keeps the stored DB value when is_sensitive=true and no
|
|
26
|
+
* value is supplied.
|
|
27
|
+
* - New key with `--sensitive` and no `--value` → exit 1.
|
|
28
|
+
*/
|
|
29
|
+
import { Command } from 'commander';
|
|
30
|
+
import * as yaml from 'yaml';
|
|
31
|
+
import { createApiClient, loadConfig } from '../../../lib/config.js';
|
|
32
|
+
import { error, formatError, success } from '../../../lib/output.js';
|
|
33
|
+
import { readVarsFromYamlFile, resolveVarsTarget, TEST_VARIABLE_CONTEXTS, TEST_VARIABLE_LIFETIMES, TEST_VARIABLE_TYPES, verifySetMutation, writeYamlFile, } from '../../../lib/test-vars.js';
|
|
34
|
+
function fail(msg) {
|
|
35
|
+
console.log(error(msg));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
function validateEnum(flag, value, allowed) {
|
|
39
|
+
if (value === undefined)
|
|
40
|
+
return;
|
|
41
|
+
if (!allowed.includes(value)) {
|
|
42
|
+
fail(`invalid value for --${flag}: '${value}'; allowed: ${allowed.join(', ')}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Determine whether the requested mutation requires the full-form shape.
|
|
47
|
+
* Any non-value flag → full form; otherwise simple form is fine for new
|
|
48
|
+
* entries (existing full-form entries always stay full form).
|
|
49
|
+
*/
|
|
50
|
+
function requiresFullForm(opts) {
|
|
51
|
+
return Boolean(opts.type || opts.lifetime || opts.context || opts.sensitive);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Coerce a CLI string argument to its likely scalar shape: integer/float when
|
|
55
|
+
* parseable, otherwise the original string. Mirrors how cope's YAML loader
|
|
56
|
+
* round-trips numeric values without quoting.
|
|
57
|
+
*/
|
|
58
|
+
function coerceScalar(s) {
|
|
59
|
+
if (s === '')
|
|
60
|
+
return s;
|
|
61
|
+
if (/^-?\d+$/.test(s)) {
|
|
62
|
+
const n = Number.parseInt(s, 10);
|
|
63
|
+
if (Number.isSafeInteger(n))
|
|
64
|
+
return n;
|
|
65
|
+
}
|
|
66
|
+
if (/^-?\d+\.\d+$/.test(s)) {
|
|
67
|
+
const n = Number.parseFloat(s);
|
|
68
|
+
if (Number.isFinite(n))
|
|
69
|
+
return n;
|
|
70
|
+
}
|
|
71
|
+
return s;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Apply common precondition checks (sensitive-without-value rules).
|
|
75
|
+
* Throws via `fail` on violation; returns the resolved sensitive-preserve flag.
|
|
76
|
+
*/
|
|
77
|
+
function checkSensitivePreconditions(options, existing) {
|
|
78
|
+
const sensitivePreserve = options.sensitive === true &&
|
|
79
|
+
options.value === undefined &&
|
|
80
|
+
existing.isFull &&
|
|
81
|
+
existing.full?.is_sensitive === true;
|
|
82
|
+
if (options.sensitive === true && options.value === undefined && !sensitivePreserve) {
|
|
83
|
+
fail(`cannot set --sensitive without --value on ${existing.raw === undefined ? 'a new key' : 'a non-sensitive key'}: '${options.key}'`);
|
|
84
|
+
}
|
|
85
|
+
return sensitivePreserve;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Apply the mutation to a parsed `Variables` object (used on the remote path
|
|
89
|
+
* and for in-memory mutations during tests). Returns the updated map.
|
|
90
|
+
*/
|
|
91
|
+
function applyMutationToObject(vars, options) {
|
|
92
|
+
const key = options.key;
|
|
93
|
+
const existingRaw = vars[key];
|
|
94
|
+
const existsAsFull = existingRaw !== undefined && typeof existingRaw === 'object' && existingRaw !== null;
|
|
95
|
+
const existing = {
|
|
96
|
+
raw: existingRaw,
|
|
97
|
+
isFull: existsAsFull,
|
|
98
|
+
isSimple: !existsAsFull && existingRaw !== undefined && existingRaw !== null,
|
|
99
|
+
full: existsAsFull ? existingRaw : undefined,
|
|
100
|
+
};
|
|
101
|
+
checkSensitivePreconditions(options, existing);
|
|
102
|
+
const upgradeToFull = requiresFullForm(options) || existsAsFull;
|
|
103
|
+
if (!upgradeToFull) {
|
|
104
|
+
if (options.value === undefined) {
|
|
105
|
+
fail('--value is required when no other flags are passed');
|
|
106
|
+
}
|
|
107
|
+
vars[key] = coerceScalar(options.value);
|
|
108
|
+
return vars;
|
|
109
|
+
}
|
|
110
|
+
if (!existsAsFull) {
|
|
111
|
+
const seed = {
|
|
112
|
+
value: options.value !== undefined
|
|
113
|
+
? coerceScalar(options.value)
|
|
114
|
+
: existing.isSimple
|
|
115
|
+
? existingRaw
|
|
116
|
+
: '',
|
|
117
|
+
type: options.type ?? 'custom',
|
|
118
|
+
lifetime: options.lifetime ?? 'test',
|
|
119
|
+
context: options.context ?? 'test',
|
|
120
|
+
is_sensitive: options.sensitive === true,
|
|
121
|
+
};
|
|
122
|
+
vars[key] = seed;
|
|
123
|
+
return vars;
|
|
124
|
+
}
|
|
125
|
+
const merged = { ...existingRaw };
|
|
126
|
+
if (options.value !== undefined)
|
|
127
|
+
merged.value = coerceScalar(options.value);
|
|
128
|
+
if (options.type !== undefined)
|
|
129
|
+
merged.type = options.type;
|
|
130
|
+
if (options.lifetime !== undefined)
|
|
131
|
+
merged.lifetime = options.lifetime;
|
|
132
|
+
if (options.context !== undefined)
|
|
133
|
+
merged.context = options.context;
|
|
134
|
+
if (options.sensitive === true)
|
|
135
|
+
merged.is_sensitive = true;
|
|
136
|
+
vars[key] = merged;
|
|
137
|
+
return vars;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Apply the mutation via the yaml.Document API so comments and key ordering
|
|
141
|
+
* survive a write back to disk.
|
|
142
|
+
*/
|
|
143
|
+
function applyMutationToDocument(doc, options) {
|
|
144
|
+
const key = options.key;
|
|
145
|
+
const existingRaw = doc.getIn(['variables', key]);
|
|
146
|
+
const existsAsFull = yaml.isMap(existingRaw);
|
|
147
|
+
const existing = {
|
|
148
|
+
raw: existingRaw,
|
|
149
|
+
isFull: existsAsFull,
|
|
150
|
+
isSimple: !existsAsFull && existingRaw !== undefined && existingRaw !== null,
|
|
151
|
+
full: existsAsFull ? existingRaw.toJSON() : undefined,
|
|
152
|
+
};
|
|
153
|
+
checkSensitivePreconditions(options, existing);
|
|
154
|
+
const upgradeToFull = requiresFullForm(options) || existsAsFull;
|
|
155
|
+
if (!doc.hasIn(['variables'])) {
|
|
156
|
+
doc.setIn(['variables'], doc.createNode({}));
|
|
157
|
+
}
|
|
158
|
+
if (!upgradeToFull) {
|
|
159
|
+
if (options.value === undefined) {
|
|
160
|
+
fail('--value is required when no other flags are passed');
|
|
161
|
+
}
|
|
162
|
+
doc.setIn(['variables', key], coerceScalar(options.value));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (!existsAsFull) {
|
|
166
|
+
const seed = {
|
|
167
|
+
value: options.value !== undefined
|
|
168
|
+
? coerceScalar(options.value)
|
|
169
|
+
: existing.isSimple
|
|
170
|
+
? existingRaw
|
|
171
|
+
: '',
|
|
172
|
+
type: options.type ?? 'custom',
|
|
173
|
+
lifetime: options.lifetime ?? 'test',
|
|
174
|
+
context: options.context ?? 'test',
|
|
175
|
+
is_sensitive: options.sensitive === true,
|
|
176
|
+
};
|
|
177
|
+
doc.setIn(['variables', key], doc.createNode(seed));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const path = ['variables', key];
|
|
181
|
+
if (options.value !== undefined)
|
|
182
|
+
doc.setIn([...path, 'value'], coerceScalar(options.value));
|
|
183
|
+
if (options.type !== undefined)
|
|
184
|
+
doc.setIn([...path, 'type'], options.type);
|
|
185
|
+
if (options.lifetime !== undefined)
|
|
186
|
+
doc.setIn([...path, 'lifetime'], options.lifetime);
|
|
187
|
+
if (options.context !== undefined)
|
|
188
|
+
doc.setIn([...path, 'context'], options.context);
|
|
189
|
+
if (options.sensitive === true)
|
|
190
|
+
doc.setIn([...path, 'is_sensitive'], true);
|
|
191
|
+
}
|
|
192
|
+
export const setCommand = new Command('set')
|
|
193
|
+
.description('Set (create or update) a typed variable on a test (local YAML or remote --id)')
|
|
194
|
+
.argument('[file]', 'Path to a local test YAML file')
|
|
195
|
+
.option('--id <uuid>', 'Remote test UUID — exports YAML, mutates, re-imports')
|
|
196
|
+
.requiredOption('--key <key>', 'Variable name')
|
|
197
|
+
.option('--value <value>', 'Variable value')
|
|
198
|
+
.option(`--type <type>`, `Variable type (one of: ${TEST_VARIABLE_TYPES.join(', ')})`)
|
|
199
|
+
.option('--lifetime <lifetime>', `Variable lifetime (one of: ${TEST_VARIABLE_LIFETIMES.join(', ')})`)
|
|
200
|
+
.option('--context <context>', `Variable context (one of: ${TEST_VARIABLE_CONTEXTS.join(', ')})`)
|
|
201
|
+
.option('--sensitive', 'Mark the variable as sensitive (masked in list output)')
|
|
202
|
+
.action(async (file, options) => {
|
|
203
|
+
try {
|
|
204
|
+
const target = resolveVarsTarget({ file, id: options.id });
|
|
205
|
+
if (!options.key)
|
|
206
|
+
fail('--key is required');
|
|
207
|
+
validateEnum('type', options.type, TEST_VARIABLE_TYPES);
|
|
208
|
+
validateEnum('lifetime', options.lifetime, TEST_VARIABLE_LIFETIMES);
|
|
209
|
+
validateEnum('context', options.context, TEST_VARIABLE_CONTEXTS);
|
|
210
|
+
if (target.kind === 'file') {
|
|
211
|
+
const { doc } = await readVarsFromYamlFile(target.path);
|
|
212
|
+
applyMutationToDocument(doc, options);
|
|
213
|
+
await writeYamlFile(target.path, doc);
|
|
214
|
+
console.log(success(`Set variable '${options.key}' in ${target.path}`));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// Remote --id path: read-modify-write via export → import.
|
|
218
|
+
const config = await loadConfig();
|
|
219
|
+
const client = createApiClient(config);
|
|
220
|
+
const yamlText = await client.exportTest(target.uuid, 'yaml', false);
|
|
221
|
+
const def = yaml.parse(yamlText) ?? {};
|
|
222
|
+
def.variables = applyMutationToObject(def.variables ?? {}, options);
|
|
223
|
+
const expectedEntry = def.variables[options.key];
|
|
224
|
+
const result = await client.importTestDefinition([def]);
|
|
225
|
+
if (result?.success === false) {
|
|
226
|
+
fail(`import failed: ${JSON.stringify(result)}`);
|
|
227
|
+
}
|
|
228
|
+
const action = result?.imported?.[0]?.action;
|
|
229
|
+
if (action === 'conflict') {
|
|
230
|
+
const message = result?.imported?.[0]?.message;
|
|
231
|
+
fail(`import returned conflict for test ${target.uuid}${message ? `: ${message}` : ''} — variable '${options.key}' was not updated`);
|
|
232
|
+
}
|
|
233
|
+
// Re-export and verify the mutation took effect. `ImportResult.success`
|
|
234
|
+
// can be `true` while `action` is `unchanged`/`conflict`, so a green
|
|
235
|
+
// import response is not enough — we must observe the change server-side.
|
|
236
|
+
const reExportText = await client.exportTest(target.uuid, 'yaml', false);
|
|
237
|
+
const reExportDef = yaml.parse(reExportText) ?? {};
|
|
238
|
+
const verifyError = verifySetMutation(reExportDef.variables ?? {}, options.key, expectedEntry);
|
|
239
|
+
if (verifyError !== null) {
|
|
240
|
+
fail(`post-import verification failed: ${verifyError}${action ? ` (import action: ${action})` : ''}`);
|
|
241
|
+
}
|
|
242
|
+
console.log(success(`Set variable '${options.key}' on test ${target.uuid}`));
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
console.log(error(`Failed to set variable: ${formatError(err)}`));
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
//# sourceMappingURL=set.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"set.js","sourceRoot":"","sources":["../../../../../../src/cli/commands/test/vars/set.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qa-use test vars unset - Remove a typed variable from a test.
|
|
3
|
+
*
|
|
4
|
+
* Local-file path: deletes the key via the yaml.Document API. When the
|
|
5
|
+
* `variables:` map becomes empty, the whole block is removed.
|
|
6
|
+
*
|
|
7
|
+
* Remote `--id` path: export → mutate (delete the key) → import. Absent keys
|
|
8
|
+
* are a no-op on either path (exit 0 with an info note).
|
|
9
|
+
*/
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
export declare const unsetCommand: Command;
|
|
12
|
+
//# sourceMappingURL=unset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unset.d.ts","sourceRoot":"","sources":["../../../../../../src/cli/commands/test/vars/unset.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsBpC,eAAO,MAAM,YAAY,SAiFrB,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qa-use test vars unset - Remove a typed variable from a test.
|
|
3
|
+
*
|
|
4
|
+
* Local-file path: deletes the key via the yaml.Document API. When the
|
|
5
|
+
* `variables:` map becomes empty, the whole block is removed.
|
|
6
|
+
*
|
|
7
|
+
* Remote `--id` path: export → mutate (delete the key) → import. Absent keys
|
|
8
|
+
* are a no-op on either path (exit 0 with an info note).
|
|
9
|
+
*/
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import * as yaml from 'yaml';
|
|
12
|
+
import { createApiClient, loadConfig } from '../../../lib/config.js';
|
|
13
|
+
import { error, formatError, info, success } from '../../../lib/output.js';
|
|
14
|
+
import { readVarsFromYamlFile, resolveVarsTarget, verifyUnsetMutation, writeYamlFile, } from '../../../lib/test-vars.js';
|
|
15
|
+
function fail(msg) {
|
|
16
|
+
console.log(error(msg));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
export const unsetCommand = new Command('unset')
|
|
20
|
+
.description('Remove a typed variable from a test (local YAML or remote --id)')
|
|
21
|
+
.argument('[file]', 'Path to a local test YAML file')
|
|
22
|
+
.option('--id <uuid>', 'Remote test UUID — exports YAML, mutates, re-imports')
|
|
23
|
+
.requiredOption('--key <key>', 'Variable name')
|
|
24
|
+
.action(async (file, options) => {
|
|
25
|
+
try {
|
|
26
|
+
const target = resolveVarsTarget({ file, id: options.id });
|
|
27
|
+
if (!options.key)
|
|
28
|
+
fail('--key is required');
|
|
29
|
+
const key = options.key;
|
|
30
|
+
if (target.kind === 'file') {
|
|
31
|
+
const { doc } = await readVarsFromYamlFile(target.path);
|
|
32
|
+
const exists = doc.hasIn(['variables', key]);
|
|
33
|
+
if (!exists) {
|
|
34
|
+
console.log(info(`key '${key}' not present in ${target.path}`));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
doc.deleteIn(['variables', key]);
|
|
38
|
+
// If the variables map is now empty, drop the whole block.
|
|
39
|
+
const remaining = doc.getIn(['variables']);
|
|
40
|
+
const isEmpty = remaining === null ||
|
|
41
|
+
remaining === undefined ||
|
|
42
|
+
(yaml.isMap(remaining) && remaining.items.length === 0);
|
|
43
|
+
if (isEmpty) {
|
|
44
|
+
doc.deleteIn(['variables']);
|
|
45
|
+
}
|
|
46
|
+
await writeYamlFile(target.path, doc);
|
|
47
|
+
console.log(success(`Unset variable '${key}' in ${target.path}`));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Remote --id path
|
|
51
|
+
const config = await loadConfig();
|
|
52
|
+
const client = createApiClient(config);
|
|
53
|
+
const yamlText = await client.exportTest(target.uuid, 'yaml', false);
|
|
54
|
+
const def = yaml.parse(yamlText) ?? {};
|
|
55
|
+
const vars = def.variables ?? {};
|
|
56
|
+
if (!(key in vars)) {
|
|
57
|
+
console.log(info(`key '${key}' not present on test ${target.uuid}`));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
delete vars[key];
|
|
61
|
+
def.variables = vars;
|
|
62
|
+
const result = await client.importTestDefinition([def]);
|
|
63
|
+
if (result?.success === false) {
|
|
64
|
+
fail(`import failed: ${JSON.stringify(result)}`);
|
|
65
|
+
}
|
|
66
|
+
const action = result?.imported?.[0]?.action;
|
|
67
|
+
if (action === 'conflict') {
|
|
68
|
+
const message = result?.imported?.[0]?.message;
|
|
69
|
+
fail(`import returned conflict for test ${target.uuid}${message ? `: ${message}` : ''} — variable '${key}' was not removed`);
|
|
70
|
+
}
|
|
71
|
+
// Re-export and verify the deletion took effect — see set.ts for why
|
|
72
|
+
// `ImportResult.success === true` is not a strong enough signal.
|
|
73
|
+
const reExportText = await client.exportTest(target.uuid, 'yaml', false);
|
|
74
|
+
const reExportDef = yaml.parse(reExportText) ?? {};
|
|
75
|
+
const verifyError = verifyUnsetMutation(reExportDef.variables ?? {}, key);
|
|
76
|
+
if (verifyError !== null) {
|
|
77
|
+
fail(`post-import verification failed: ${verifyError}${action ? ` (import action: ${action})` : ''}`);
|
|
78
|
+
}
|
|
79
|
+
console.log(success(`Unset variable '${key}' on test ${target.uuid}`));
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
console.log(error(`Failed to unset variable: ${formatError(err)}`));
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=unset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unset.js","sourceRoot":"","sources":["../../../../../../src/cli/commands/test/vars/unset.ts"],"names":[],"mappings":"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"}
|