@xano/cli 0.0.34 → 0.0.36
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/commands/unit_test/list/index.d.ts +15 -0
- package/dist/commands/unit_test/list/index.js +137 -0
- package/dist/commands/unit_test/run/index.d.ts +16 -0
- package/dist/commands/unit_test/run/index.js +121 -0
- package/dist/commands/unit_test/run_all/index.d.ts +15 -0
- package/dist/commands/unit_test/run_all/index.js +215 -0
- package/dist/commands/workflow_test/run_all/index.js +10 -11
- package/oclif.manifest.json +2145 -1887
- package/package.json +4 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class UnitTestList extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
'obj-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
private loadCredentials;
|
|
15
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import * as yaml from 'js-yaml';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import BaseCommand from '../../../base-command.js';
|
|
7
|
+
export default class UnitTestList extends BaseCommand {
|
|
8
|
+
static description = 'List all unit tests in a workspace';
|
|
9
|
+
static examples = [
|
|
10
|
+
`$ xano unit-test list
|
|
11
|
+
Unit tests in workspace 5:
|
|
12
|
+
- my-test (ID: abc-123) [function: math]
|
|
13
|
+
- auth-check (ID: def-456) [query: /user/login]
|
|
14
|
+
`,
|
|
15
|
+
`$ xano unit-test list -w 5 --output json`,
|
|
16
|
+
`$ xano unit-test list --obj-type function`,
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
...BaseCommand.baseFlags,
|
|
20
|
+
branch: Flags.string({
|
|
21
|
+
char: 'b',
|
|
22
|
+
description: 'Filter by branch name',
|
|
23
|
+
required: false,
|
|
24
|
+
}),
|
|
25
|
+
'obj-type': Flags.string({
|
|
26
|
+
description: 'Filter by object type',
|
|
27
|
+
options: ['function', 'query', 'middleware'],
|
|
28
|
+
required: false,
|
|
29
|
+
}),
|
|
30
|
+
output: Flags.string({
|
|
31
|
+
char: 'o',
|
|
32
|
+
default: 'summary',
|
|
33
|
+
description: 'Output format',
|
|
34
|
+
options: ['summary', 'json'],
|
|
35
|
+
required: false,
|
|
36
|
+
}),
|
|
37
|
+
workspace: Flags.string({
|
|
38
|
+
char: 'w',
|
|
39
|
+
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
40
|
+
required: false,
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
async run() {
|
|
44
|
+
const { flags } = await this.parse(UnitTestList);
|
|
45
|
+
const profileName = flags.profile || this.getDefaultProfile();
|
|
46
|
+
const credentials = this.loadCredentials();
|
|
47
|
+
if (!(profileName in credentials.profiles)) {
|
|
48
|
+
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
49
|
+
`Create a profile using 'xano profile create'`);
|
|
50
|
+
}
|
|
51
|
+
const profile = credentials.profiles[profileName];
|
|
52
|
+
if (!profile.instance_origin) {
|
|
53
|
+
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
54
|
+
}
|
|
55
|
+
if (!profile.access_token) {
|
|
56
|
+
this.error(`Profile '${profileName}' is missing access_token`);
|
|
57
|
+
}
|
|
58
|
+
const workspaceId = flags.workspace || profile.workspace;
|
|
59
|
+
if (!workspaceId) {
|
|
60
|
+
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
61
|
+
}
|
|
62
|
+
const params = new URLSearchParams();
|
|
63
|
+
params.set('per_page', '10000');
|
|
64
|
+
if (flags.branch) {
|
|
65
|
+
params.set('branch', flags.branch);
|
|
66
|
+
}
|
|
67
|
+
if (flags['obj-type']) {
|
|
68
|
+
params.set('obj_type', flags['obj-type']);
|
|
69
|
+
}
|
|
70
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/unit_test?${params}`;
|
|
71
|
+
try {
|
|
72
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
73
|
+
headers: {
|
|
74
|
+
'accept': 'application/json',
|
|
75
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
76
|
+
},
|
|
77
|
+
method: 'GET',
|
|
78
|
+
}, flags.verbose, profile.access_token);
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
const errorText = await response.text();
|
|
81
|
+
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
82
|
+
}
|
|
83
|
+
const data = await response.json();
|
|
84
|
+
let tests;
|
|
85
|
+
if (Array.isArray(data)) {
|
|
86
|
+
tests = data;
|
|
87
|
+
}
|
|
88
|
+
else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
|
|
89
|
+
tests = data.items;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
this.error('Unexpected API response format');
|
|
93
|
+
}
|
|
94
|
+
if (flags.output === 'json') {
|
|
95
|
+
this.log(JSON.stringify(tests, null, 2));
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
if (tests.length === 0) {
|
|
99
|
+
this.log('No unit tests found');
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this.log(`Unit tests in workspace ${workspaceId}:`);
|
|
103
|
+
for (const test of tests) {
|
|
104
|
+
this.log(` - ${test.name} (ID: ${test.id}) [${test.obj_type}: ${test.obj_name}]`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (error instanceof Error) {
|
|
111
|
+
this.error(`Failed to list unit tests: ${error.message}`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
this.error(`Failed to list unit tests: ${String(error)}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
loadCredentials() {
|
|
119
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
120
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
121
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
122
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
123
|
+
`Create a profile using 'xano profile create'`);
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
127
|
+
const parsed = yaml.load(fileContent);
|
|
128
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
129
|
+
this.error('Credentials file has invalid format.');
|
|
130
|
+
}
|
|
131
|
+
return parsed;
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class UnitTestRun extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
unit_test_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
private loadCredentials;
|
|
16
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import * as yaml from 'js-yaml';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import BaseCommand from '../../../base-command.js';
|
|
7
|
+
export default class UnitTestRun extends BaseCommand {
|
|
8
|
+
static args = {
|
|
9
|
+
unit_test_id: Args.string({
|
|
10
|
+
description: 'ID of the unit test to run',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static description = 'Run a unit test';
|
|
15
|
+
static examples = [
|
|
16
|
+
`$ xano unit-test run abc-123
|
|
17
|
+
Running unit test abc-123...
|
|
18
|
+
Result: PASS
|
|
19
|
+
`,
|
|
20
|
+
`$ xano unit-test run abc-123 -o json`,
|
|
21
|
+
];
|
|
22
|
+
static flags = {
|
|
23
|
+
...BaseCommand.baseFlags,
|
|
24
|
+
output: Flags.string({
|
|
25
|
+
char: 'o',
|
|
26
|
+
default: 'summary',
|
|
27
|
+
description: 'Output format',
|
|
28
|
+
options: ['summary', 'json'],
|
|
29
|
+
required: false,
|
|
30
|
+
}),
|
|
31
|
+
workspace: Flags.string({
|
|
32
|
+
char: 'w',
|
|
33
|
+
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
34
|
+
required: false,
|
|
35
|
+
}),
|
|
36
|
+
};
|
|
37
|
+
async run() {
|
|
38
|
+
const { args, flags } = await this.parse(UnitTestRun);
|
|
39
|
+
const profileName = flags.profile || this.getDefaultProfile();
|
|
40
|
+
const credentials = this.loadCredentials();
|
|
41
|
+
if (!(profileName in credentials.profiles)) {
|
|
42
|
+
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
43
|
+
`Create a profile using 'xano profile create'`);
|
|
44
|
+
}
|
|
45
|
+
const profile = credentials.profiles[profileName];
|
|
46
|
+
if (!profile.instance_origin) {
|
|
47
|
+
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
48
|
+
}
|
|
49
|
+
if (!profile.access_token) {
|
|
50
|
+
this.error(`Profile '${profileName}' is missing access_token`);
|
|
51
|
+
}
|
|
52
|
+
const workspaceId = flags.workspace || profile.workspace;
|
|
53
|
+
if (!workspaceId) {
|
|
54
|
+
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
55
|
+
}
|
|
56
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/unit_test/${args.unit_test_id}/run`;
|
|
57
|
+
try {
|
|
58
|
+
if (flags.output === 'summary') {
|
|
59
|
+
this.log(`Running unit test ${args.unit_test_id}...`);
|
|
60
|
+
}
|
|
61
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
62
|
+
headers: {
|
|
63
|
+
'accept': 'application/json',
|
|
64
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
65
|
+
'Content-Type': 'application/json',
|
|
66
|
+
},
|
|
67
|
+
method: 'POST',
|
|
68
|
+
}, flags.verbose, profile.access_token);
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
const errorText = await response.text();
|
|
71
|
+
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
72
|
+
}
|
|
73
|
+
const result = await response.json();
|
|
74
|
+
if (flags.output === 'json') {
|
|
75
|
+
this.log(JSON.stringify(result, null, 2));
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
if (result.status === 'ok') {
|
|
79
|
+
this.log('Result: PASS');
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
this.log('Result: FAIL');
|
|
83
|
+
const failedExpects = result.results?.filter(r => r.status === 'fail') ?? [];
|
|
84
|
+
for (const expect of failedExpects) {
|
|
85
|
+
if (expect.message) {
|
|
86
|
+
this.log(` Error: ${expect.message}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
this.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (error instanceof Error) {
|
|
95
|
+
this.error(`Failed to run unit test: ${error.message}`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
this.error(`Failed to run unit test: ${String(error)}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
loadCredentials() {
|
|
103
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
104
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
105
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
106
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
107
|
+
`Create a profile using 'xano profile create'`);
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
111
|
+
const parsed = yaml.load(fileContent);
|
|
112
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
113
|
+
this.error('Credentials file has invalid format.');
|
|
114
|
+
}
|
|
115
|
+
return parsed;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class UnitTestRunAll extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
'obj-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
private loadCredentials;
|
|
15
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import * as yaml from 'js-yaml';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import BaseCommand from '../../../base-command.js';
|
|
7
|
+
export default class UnitTestRunAll extends BaseCommand {
|
|
8
|
+
static description = 'Run all unit tests in a workspace';
|
|
9
|
+
static examples = [
|
|
10
|
+
`$ xano unit-test run-all
|
|
11
|
+
Running 5 unit tests...
|
|
12
|
+
|
|
13
|
+
PASS my-test [function: math]
|
|
14
|
+
PASS auth-check [query: /user/login]
|
|
15
|
+
FAIL data-validation [function: validate]
|
|
16
|
+
Error: assertion failed
|
|
17
|
+
|
|
18
|
+
Results: 2 passed, 1 failed
|
|
19
|
+
`,
|
|
20
|
+
`$ xano unit-test run-all --obj-type function -o json`,
|
|
21
|
+
];
|
|
22
|
+
static flags = {
|
|
23
|
+
...BaseCommand.baseFlags,
|
|
24
|
+
branch: Flags.string({
|
|
25
|
+
char: 'b',
|
|
26
|
+
description: 'Filter by branch name',
|
|
27
|
+
required: false,
|
|
28
|
+
}),
|
|
29
|
+
'obj-type': Flags.string({
|
|
30
|
+
description: 'Filter by object type',
|
|
31
|
+
options: ['function', 'query', 'middleware'],
|
|
32
|
+
required: false,
|
|
33
|
+
}),
|
|
34
|
+
output: Flags.string({
|
|
35
|
+
char: 'o',
|
|
36
|
+
default: 'summary',
|
|
37
|
+
description: 'Output format',
|
|
38
|
+
options: ['summary', 'json'],
|
|
39
|
+
required: false,
|
|
40
|
+
}),
|
|
41
|
+
workspace: Flags.string({
|
|
42
|
+
char: 'w',
|
|
43
|
+
description: 'Workspace ID (uses profile workspace if not provided)',
|
|
44
|
+
required: false,
|
|
45
|
+
}),
|
|
46
|
+
};
|
|
47
|
+
async run() {
|
|
48
|
+
const { flags } = await this.parse(UnitTestRunAll);
|
|
49
|
+
const profileName = flags.profile || this.getDefaultProfile();
|
|
50
|
+
const credentials = this.loadCredentials();
|
|
51
|
+
if (!(profileName in credentials.profiles)) {
|
|
52
|
+
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
53
|
+
`Create a profile using 'xano profile create'`);
|
|
54
|
+
}
|
|
55
|
+
const profile = credentials.profiles[profileName];
|
|
56
|
+
if (!profile.instance_origin) {
|
|
57
|
+
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
58
|
+
}
|
|
59
|
+
if (!profile.access_token) {
|
|
60
|
+
this.error(`Profile '${profileName}' is missing access_token`);
|
|
61
|
+
}
|
|
62
|
+
const workspaceId = flags.workspace || profile.workspace;
|
|
63
|
+
if (!workspaceId) {
|
|
64
|
+
this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
|
|
65
|
+
}
|
|
66
|
+
const baseUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/unit_test`;
|
|
67
|
+
try {
|
|
68
|
+
// Step 1: List all unit tests
|
|
69
|
+
const listParams = new URLSearchParams();
|
|
70
|
+
listParams.set('per_page', '10000');
|
|
71
|
+
if (flags.branch) {
|
|
72
|
+
listParams.set('branch', flags.branch);
|
|
73
|
+
}
|
|
74
|
+
if (flags['obj-type']) {
|
|
75
|
+
listParams.set('obj_type', flags['obj-type']);
|
|
76
|
+
}
|
|
77
|
+
const listResponse = await this.verboseFetch(`${baseUrl}?${listParams}`, {
|
|
78
|
+
headers: {
|
|
79
|
+
accept: 'application/json',
|
|
80
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
81
|
+
},
|
|
82
|
+
method: 'GET',
|
|
83
|
+
}, flags.verbose, profile.access_token);
|
|
84
|
+
if (!listResponse.ok) {
|
|
85
|
+
const errorText = await listResponse.text();
|
|
86
|
+
this.error(`Failed to list unit tests: ${listResponse.status}: ${listResponse.statusText}\n${errorText}`);
|
|
87
|
+
}
|
|
88
|
+
const data = (await listResponse.json());
|
|
89
|
+
let tests;
|
|
90
|
+
if (Array.isArray(data)) {
|
|
91
|
+
tests = data;
|
|
92
|
+
}
|
|
93
|
+
else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
|
|
94
|
+
tests = data.items;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
this.error('Unexpected API response format');
|
|
98
|
+
}
|
|
99
|
+
if (tests.length === 0) {
|
|
100
|
+
this.log('No unit tests found');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (flags.output === 'summary') {
|
|
104
|
+
this.log(`Running ${tests.length} unit test${tests.length === 1 ? '' : 's'}...\n`);
|
|
105
|
+
}
|
|
106
|
+
// Step 2: Run each test
|
|
107
|
+
const results = [];
|
|
108
|
+
for (const test of tests) {
|
|
109
|
+
const runUrl = `${baseUrl}/${test.id}/run`;
|
|
110
|
+
try {
|
|
111
|
+
const runResponse = await this.verboseFetch(runUrl, {
|
|
112
|
+
headers: {
|
|
113
|
+
accept: 'application/json',
|
|
114
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
115
|
+
'Content-Type': 'application/json',
|
|
116
|
+
},
|
|
117
|
+
method: 'POST',
|
|
118
|
+
}, flags.verbose, profile.access_token);
|
|
119
|
+
if (!runResponse.ok) {
|
|
120
|
+
const errorText = await runResponse.text();
|
|
121
|
+
const result = {
|
|
122
|
+
message: `API error ${runResponse.status}: ${errorText}`,
|
|
123
|
+
name: test.name,
|
|
124
|
+
obj_name: test.obj_name,
|
|
125
|
+
obj_type: test.obj_type,
|
|
126
|
+
status: 'fail',
|
|
127
|
+
};
|
|
128
|
+
results.push(result);
|
|
129
|
+
if (flags.output === 'summary') {
|
|
130
|
+
this.log(`FAIL ${test.name} [${test.obj_type}: ${test.obj_name}]`);
|
|
131
|
+
this.log(` Error: API error ${runResponse.status}`);
|
|
132
|
+
}
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const runResult = (await runResponse.json());
|
|
136
|
+
const passed = runResult.status === 'ok';
|
|
137
|
+
const failedExpects = runResult.results?.filter((r) => r.status === 'fail') ?? [];
|
|
138
|
+
const result = {
|
|
139
|
+
message: failedExpects[0]?.message,
|
|
140
|
+
name: test.name,
|
|
141
|
+
obj_name: test.obj_name,
|
|
142
|
+
obj_type: test.obj_type,
|
|
143
|
+
status: passed ? 'pass' : 'fail',
|
|
144
|
+
};
|
|
145
|
+
results.push(result);
|
|
146
|
+
if (flags.output === 'summary') {
|
|
147
|
+
if (passed) {
|
|
148
|
+
this.log(`PASS ${test.name} [${test.obj_type}: ${test.obj_name}]`);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
this.log(`FAIL ${test.name} [${test.obj_type}: ${test.obj_name}]`);
|
|
152
|
+
for (const expect of failedExpects) {
|
|
153
|
+
if (expect.message) {
|
|
154
|
+
this.log(` Error: ${expect.message}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
162
|
+
results.push({
|
|
163
|
+
message,
|
|
164
|
+
name: test.name,
|
|
165
|
+
obj_name: test.obj_name,
|
|
166
|
+
obj_type: test.obj_type,
|
|
167
|
+
status: 'fail',
|
|
168
|
+
});
|
|
169
|
+
if (flags.output === 'summary') {
|
|
170
|
+
this.log(`FAIL ${test.name} [${test.obj_type}: ${test.obj_name}]`);
|
|
171
|
+
this.log(` Error: ${message}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Step 3: Summary
|
|
176
|
+
const passed = results.filter((r) => r.status === 'pass').length;
|
|
177
|
+
const failed = results.filter((r) => r.status === 'fail').length;
|
|
178
|
+
if (flags.output === 'json') {
|
|
179
|
+
this.log(JSON.stringify({ passed, failed, results }, null, 2));
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
this.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
183
|
+
}
|
|
184
|
+
if (failed > 0) {
|
|
185
|
+
process.exitCode = 1;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
if (error instanceof Error) {
|
|
190
|
+
this.error(`Failed to run unit tests: ${error.message}`);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
this.error(`Failed to run unit tests: ${String(error)}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
loadCredentials() {
|
|
198
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
199
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
200
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
201
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile create'`);
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
205
|
+
const parsed = yaml.load(fileContent);
|
|
206
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
207
|
+
this.error('Credentials file has invalid format.');
|
|
208
|
+
}
|
|
209
|
+
return parsed;
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -67,8 +67,8 @@ Results: 2 passed, 1 failed (2.691s total)
|
|
|
67
67
|
}
|
|
68
68
|
const listResponse = await this.verboseFetch(`${baseUrl}?${listParams}`, {
|
|
69
69
|
headers: {
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
accept: 'application/json',
|
|
71
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
72
72
|
},
|
|
73
73
|
method: 'GET',
|
|
74
74
|
}, flags.verbose, profile.access_token);
|
|
@@ -76,7 +76,7 @@ Results: 2 passed, 1 failed (2.691s total)
|
|
|
76
76
|
const errorText = await listResponse.text();
|
|
77
77
|
this.error(`Failed to list workflow tests: ${listResponse.status}: ${listResponse.statusText}\n${errorText}`);
|
|
78
78
|
}
|
|
79
|
-
const data = await listResponse.json();
|
|
79
|
+
const data = (await listResponse.json());
|
|
80
80
|
let tests;
|
|
81
81
|
if (Array.isArray(data)) {
|
|
82
82
|
tests = data;
|
|
@@ -102,8 +102,8 @@ Results: 2 passed, 1 failed (2.691s total)
|
|
|
102
102
|
try {
|
|
103
103
|
const runResponse = await this.verboseFetch(runUrl, {
|
|
104
104
|
headers: {
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
accept: 'application/json',
|
|
106
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
107
107
|
'Content-Type': 'application/json',
|
|
108
108
|
},
|
|
109
109
|
method: 'POST',
|
|
@@ -123,7 +123,7 @@ Results: 2 passed, 1 failed (2.691s total)
|
|
|
123
123
|
}
|
|
124
124
|
continue;
|
|
125
125
|
}
|
|
126
|
-
const runResult = await runResponse.json();
|
|
126
|
+
const runResult = (await runResponse.json());
|
|
127
127
|
const passed = runResult.status === 'ok';
|
|
128
128
|
const result = {
|
|
129
129
|
message: runResult.message,
|
|
@@ -161,8 +161,8 @@ Results: 2 passed, 1 failed (2.691s total)
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
// Step 3: Summary
|
|
164
|
-
const passed = results.filter(r => r.status === 'pass').length;
|
|
165
|
-
const failed = results.filter(r => r.status === 'fail').length;
|
|
164
|
+
const passed = results.filter((r) => r.status === 'pass').length;
|
|
165
|
+
const failed = results.filter((r) => r.status === 'fail').length;
|
|
166
166
|
if (flags.output === 'json') {
|
|
167
167
|
this.log(JSON.stringify({ passed, failed, total_timing: totalTiming, results }, null, 2));
|
|
168
168
|
}
|
|
@@ -170,7 +170,7 @@ Results: 2 passed, 1 failed (2.691s total)
|
|
|
170
170
|
this.log(`\nResults: ${passed} passed, ${failed} failed (${totalTiming.toFixed(3)}s total)`);
|
|
171
171
|
}
|
|
172
172
|
if (failed > 0) {
|
|
173
|
-
|
|
173
|
+
process.exitCode = 1;
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
catch (error) {
|
|
@@ -186,8 +186,7 @@ Results: 2 passed, 1 failed (2.691s total)
|
|
|
186
186
|
const configDir = path.join(os.homedir(), '.xano');
|
|
187
187
|
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
188
188
|
if (!fs.existsSync(credentialsPath)) {
|
|
189
|
-
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
190
|
-
`Create a profile using 'xano profile create'`);
|
|
189
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile create'`);
|
|
191
190
|
}
|
|
192
191
|
try {
|
|
193
192
|
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|