@dk/jolly 0.1.6 → 0.1.8
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/bootstrap.js +40 -16
- package/dist/index.js +54 -30
- package/package.json +1 -1
- package/src/api/client.ts +95 -21
- package/src/commands/store.ts +16 -16
- package/src/test/command-handlers.test.ts +34 -29
- package/src/test/e2e-flows.test.ts +50 -31
- package/src/test/entry-points.test.ts +4 -1
- package/src/test/error-handling.test.ts +8 -8
- package/src/test/mocks.ts +55 -15
package/dist/bootstrap.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/api/client.ts
|
|
4
4
|
class SaleorCloudClient {
|
|
5
|
-
baseUrl = "https://cloud.saleor.io/api";
|
|
5
|
+
baseUrl = "https://cloud.saleor.io/api/v1";
|
|
6
6
|
token;
|
|
7
7
|
constructor(token) {
|
|
8
8
|
this.token = token || process.env.SALEOR_CLOUD_TOKEN || "";
|
|
@@ -11,43 +11,67 @@ class SaleorCloudClient {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
async request(endpoint, options) {
|
|
14
|
-
const
|
|
14
|
+
const mergedOptions = {
|
|
15
|
+
method: options?.method || "GET",
|
|
15
16
|
...options,
|
|
16
17
|
headers: {
|
|
17
|
-
Authorization: `
|
|
18
|
+
Authorization: `Token ${this.token}`,
|
|
18
19
|
"Content-Type": "application/json",
|
|
19
20
|
...options?.headers
|
|
20
21
|
}
|
|
21
|
-
}
|
|
22
|
+
};
|
|
23
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`, mergedOptions);
|
|
22
24
|
if (!response.ok) {
|
|
23
|
-
|
|
25
|
+
const body = await response.text();
|
|
26
|
+
const truncatedBody = body.length > 200 ? body.substring(0, 200) + "..." : body;
|
|
27
|
+
throw new Error(`API error: ${response.status} ${response.statusText} - ${truncatedBody}`);
|
|
24
28
|
}
|
|
25
29
|
return response.json();
|
|
26
30
|
}
|
|
27
|
-
async
|
|
28
|
-
return this.request("/
|
|
31
|
+
async getOrganizations() {
|
|
32
|
+
return this.request("/organizations");
|
|
29
33
|
}
|
|
30
|
-
async
|
|
31
|
-
return this.request(
|
|
34
|
+
async getProjects(organizationSlug) {
|
|
35
|
+
return this.request(`/organizations/${organizationSlug}/projects`);
|
|
36
|
+
}
|
|
37
|
+
async createProject(organizationSlug, name, region) {
|
|
38
|
+
return this.request(`/organizations/${organizationSlug}/projects`, {
|
|
32
39
|
method: "POST",
|
|
33
40
|
body: JSON.stringify({ name, region })
|
|
34
41
|
});
|
|
35
42
|
}
|
|
36
|
-
async getEnvironments(
|
|
37
|
-
return this.request(`/
|
|
43
|
+
async getEnvironments(organizationSlug, projectSlug) {
|
|
44
|
+
return this.request(`/organizations/${organizationSlug}/projects/${projectSlug}/environments`);
|
|
38
45
|
}
|
|
39
|
-
async createEnvironment(
|
|
40
|
-
return this.request(`/
|
|
46
|
+
async createEnvironment(organizationSlug, projectSlug, name, region) {
|
|
47
|
+
return this.request(`/organizations/${organizationSlug}/projects/${projectSlug}/environments`, {
|
|
41
48
|
method: "POST",
|
|
42
|
-
body: JSON.stringify({ name })
|
|
49
|
+
body: JSON.stringify({ name, region })
|
|
43
50
|
});
|
|
44
51
|
}
|
|
52
|
+
async getEnvironment(organizationSlug, projectSlug, environmentSlug) {
|
|
53
|
+
return this.request(`/organizations/${organizationSlug}/projects/${projectSlug}/environments/${environmentSlug}`);
|
|
54
|
+
}
|
|
45
55
|
async registerApp(environmentId, appType, name) {
|
|
46
56
|
return this.request(`/environments/${environmentId}/apps`, {
|
|
47
57
|
method: "POST",
|
|
48
58
|
body: JSON.stringify({ type: appType, name })
|
|
49
59
|
});
|
|
50
60
|
}
|
|
61
|
+
async getStores() {
|
|
62
|
+
return this.getOrganizations();
|
|
63
|
+
}
|
|
64
|
+
async createStore(name, region = "us-east-1") {
|
|
65
|
+
const { organizations } = await this.getOrganizations();
|
|
66
|
+
if (organizations.length === 0) {
|
|
67
|
+
throw new Error("No organizations found. Create one at https://cloud.saleor.io");
|
|
68
|
+
}
|
|
69
|
+
return this.createProject(organizations[0].slug, name, region);
|
|
70
|
+
}
|
|
71
|
+
async createEnvironmentFromStore(storeId, name) {
|
|
72
|
+
const { environments } = await this.getEnvironments(storeId, "default");
|
|
73
|
+
return this.createEnvironment(storeId, environments.length > 0 ? environments[0].project?.slug || "default" : "default", name, "default");
|
|
74
|
+
}
|
|
51
75
|
}
|
|
52
76
|
|
|
53
77
|
// src/api/auth.ts
|
|
@@ -129,8 +153,8 @@ async function createStore(name, region) {
|
|
|
129
153
|
try {
|
|
130
154
|
const result = await client.createStore(name, region);
|
|
131
155
|
console.log(success(`Store created successfully!`));
|
|
132
|
-
console.log(info(`
|
|
133
|
-
console.log(info(`Dashboard: https://cloud.saleor.io/
|
|
156
|
+
console.log(info(`Project slug: ${result.project.slug}`));
|
|
157
|
+
console.log(info(`Dashboard: https://cloud.saleor.io/organizations/default/projects/${result.project.slug}`));
|
|
134
158
|
} catch (err) {
|
|
135
159
|
console.log(error(`Failed to create store: ${err}`));
|
|
136
160
|
process.exit(1);
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ import { hideBin } from "yargs/helpers";
|
|
|
24
24
|
|
|
25
25
|
// src/api/client.ts
|
|
26
26
|
class SaleorCloudClient {
|
|
27
|
-
baseUrl = "https://cloud.saleor.io/api";
|
|
27
|
+
baseUrl = "https://cloud.saleor.io/api/v1";
|
|
28
28
|
token;
|
|
29
29
|
constructor(token) {
|
|
30
30
|
this.token = token || process.env.SALEOR_CLOUD_TOKEN || "";
|
|
@@ -33,43 +33,67 @@ class SaleorCloudClient {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
async request(endpoint, options) {
|
|
36
|
-
const
|
|
36
|
+
const mergedOptions = {
|
|
37
|
+
method: options?.method || "GET",
|
|
37
38
|
...options,
|
|
38
39
|
headers: {
|
|
39
|
-
Authorization: `
|
|
40
|
+
Authorization: `Token ${this.token}`,
|
|
40
41
|
"Content-Type": "application/json",
|
|
41
42
|
...options?.headers
|
|
42
43
|
}
|
|
43
|
-
}
|
|
44
|
+
};
|
|
45
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`, mergedOptions);
|
|
44
46
|
if (!response.ok) {
|
|
45
|
-
|
|
47
|
+
const body = await response.text();
|
|
48
|
+
const truncatedBody = body.length > 200 ? body.substring(0, 200) + "..." : body;
|
|
49
|
+
throw new Error(`API error: ${response.status} ${response.statusText} - ${truncatedBody}`);
|
|
46
50
|
}
|
|
47
51
|
return response.json();
|
|
48
52
|
}
|
|
49
|
-
async
|
|
50
|
-
return this.request("/
|
|
53
|
+
async getOrganizations() {
|
|
54
|
+
return this.request("/organizations");
|
|
51
55
|
}
|
|
52
|
-
async
|
|
53
|
-
return this.request(
|
|
56
|
+
async getProjects(organizationSlug) {
|
|
57
|
+
return this.request(`/organizations/${organizationSlug}/projects`);
|
|
58
|
+
}
|
|
59
|
+
async createProject(organizationSlug, name, region) {
|
|
60
|
+
return this.request(`/organizations/${organizationSlug}/projects`, {
|
|
54
61
|
method: "POST",
|
|
55
62
|
body: JSON.stringify({ name, region })
|
|
56
63
|
});
|
|
57
64
|
}
|
|
58
|
-
async getEnvironments(
|
|
59
|
-
return this.request(`/
|
|
65
|
+
async getEnvironments(organizationSlug, projectSlug) {
|
|
66
|
+
return this.request(`/organizations/${organizationSlug}/projects/${projectSlug}/environments`);
|
|
60
67
|
}
|
|
61
|
-
async createEnvironment(
|
|
62
|
-
return this.request(`/
|
|
68
|
+
async createEnvironment(organizationSlug, projectSlug, name, region) {
|
|
69
|
+
return this.request(`/organizations/${organizationSlug}/projects/${projectSlug}/environments`, {
|
|
63
70
|
method: "POST",
|
|
64
|
-
body: JSON.stringify({ name })
|
|
71
|
+
body: JSON.stringify({ name, region })
|
|
65
72
|
});
|
|
66
73
|
}
|
|
74
|
+
async getEnvironment(organizationSlug, projectSlug, environmentSlug) {
|
|
75
|
+
return this.request(`/organizations/${organizationSlug}/projects/${projectSlug}/environments/${environmentSlug}`);
|
|
76
|
+
}
|
|
67
77
|
async registerApp(environmentId, appType, name) {
|
|
68
78
|
return this.request(`/environments/${environmentId}/apps`, {
|
|
69
79
|
method: "POST",
|
|
70
80
|
body: JSON.stringify({ type: appType, name })
|
|
71
81
|
});
|
|
72
82
|
}
|
|
83
|
+
async getStores() {
|
|
84
|
+
return this.getOrganizations();
|
|
85
|
+
}
|
|
86
|
+
async createStore(name, region = "us-east-1") {
|
|
87
|
+
const { organizations } = await this.getOrganizations();
|
|
88
|
+
if (organizations.length === 0) {
|
|
89
|
+
throw new Error("No organizations found. Create one at https://cloud.saleor.io");
|
|
90
|
+
}
|
|
91
|
+
return this.createProject(organizations[0].slug, name, region);
|
|
92
|
+
}
|
|
93
|
+
async createEnvironmentFromStore(storeId, name) {
|
|
94
|
+
const { environments } = await this.getEnvironments(storeId, "default");
|
|
95
|
+
return this.createEnvironment(storeId, environments.length > 0 ? environments[0].project?.slug || "default" : "default", name, "default");
|
|
96
|
+
}
|
|
73
97
|
}
|
|
74
98
|
|
|
75
99
|
// src/api/auth.ts
|
|
@@ -198,8 +222,8 @@ async function createStore(name, region) {
|
|
|
198
222
|
try {
|
|
199
223
|
const result = await client.createStore(name, region);
|
|
200
224
|
console.log(success(`Store created successfully!`));
|
|
201
|
-
console.log(info(`
|
|
202
|
-
console.log(info(`Dashboard: https://cloud.saleor.io/
|
|
225
|
+
console.log(info(`Project slug: ${result.project.slug}`));
|
|
226
|
+
console.log(info(`Dashboard: https://cloud.saleor.io/organizations/default/projects/${result.project.slug}`));
|
|
203
227
|
} catch (err) {
|
|
204
228
|
console.log(error(`Failed to create store: ${err}`));
|
|
205
229
|
process.exit(1);
|
|
@@ -208,19 +232,19 @@ async function createStore(name, region) {
|
|
|
208
232
|
async function listStores() {
|
|
209
233
|
const token = requireToken();
|
|
210
234
|
const client = new SaleorCloudClient(token);
|
|
211
|
-
console.log(info("Fetching
|
|
235
|
+
console.log(info("Fetching organizations..."));
|
|
212
236
|
try {
|
|
213
|
-
const
|
|
214
|
-
if (
|
|
215
|
-
console.log(info("No
|
|
237
|
+
const { organizations } = await client.getOrganizations();
|
|
238
|
+
if (organizations.length === 0) {
|
|
239
|
+
console.log(info("No organizations found. Create one at https://cloud.saleor.io"));
|
|
216
240
|
return;
|
|
217
241
|
}
|
|
218
|
-
console.log(success(`Found ${
|
|
242
|
+
console.log(success(`Found ${organizations.length} organization(s):
|
|
219
243
|
`));
|
|
220
|
-
for (const
|
|
221
|
-
console.log(` ${
|
|
222
|
-
console.log(`
|
|
223
|
-
console.log(` Created: ${new Date(
|
|
244
|
+
for (const org of organizations) {
|
|
245
|
+
console.log(` ${org.name} (${org.slug})`);
|
|
246
|
+
console.log(` Email: ${org.owner_email}`);
|
|
247
|
+
console.log(` Created: ${new Date(org.created).toLocaleDateString()}`);
|
|
224
248
|
console.log();
|
|
225
249
|
}
|
|
226
250
|
} catch (err) {
|
|
@@ -228,15 +252,15 @@ async function listStores() {
|
|
|
228
252
|
process.exit(1);
|
|
229
253
|
}
|
|
230
254
|
}
|
|
231
|
-
async function createEnvironment(
|
|
255
|
+
async function createEnvironment(organizationSlug, name) {
|
|
232
256
|
const token = requireToken();
|
|
233
257
|
const client = new SaleorCloudClient(token);
|
|
234
|
-
console.log(info(`Creating environment: ${name} for
|
|
258
|
+
console.log(info(`Creating environment: ${name} for organization ${organizationSlug}...`));
|
|
235
259
|
try {
|
|
236
|
-
const
|
|
260
|
+
const { environment } = await client.createEnvironment(organizationSlug, "default", name, "us-east-1");
|
|
237
261
|
console.log(success(`Environment created successfully!`));
|
|
238
|
-
console.log(info(`Environment
|
|
239
|
-
console.log(info(`
|
|
262
|
+
console.log(info(`Environment key: ${environment.key}`));
|
|
263
|
+
console.log(info(`Domain: ${environment.domain}`));
|
|
240
264
|
} catch (err) {
|
|
241
265
|
console.log(error(`Failed to create environment: ${err}`));
|
|
242
266
|
process.exit(1);
|
package/package.json
CHANGED
package/src/api/client.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export class SaleorCloudClient {
|
|
2
|
-
private baseUrl = 'https://cloud.saleor.io/api';
|
|
2
|
+
private baseUrl = 'https://cloud.saleor.io/api/v1';
|
|
3
3
|
private token: string;
|
|
4
4
|
|
|
5
5
|
constructor(token?: string) {
|
|
@@ -10,63 +10,137 @@ export class SaleorCloudClient {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
async request<T>(endpoint: string, options?: RequestInit): Promise<T> {
|
|
13
|
-
const
|
|
13
|
+
const mergedOptions: RequestInit = {
|
|
14
|
+
method: options?.method || 'GET',
|
|
14
15
|
...options,
|
|
15
16
|
headers: {
|
|
16
|
-
'Authorization': `
|
|
17
|
+
'Authorization': `Token ${this.token}`,
|
|
17
18
|
'Content-Type': 'application/json',
|
|
18
19
|
...options?.headers,
|
|
19
20
|
},
|
|
20
|
-
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`, mergedOptions);
|
|
21
24
|
|
|
22
25
|
if (!response.ok) {
|
|
23
|
-
|
|
26
|
+
const body = await response.text();
|
|
27
|
+
const truncatedBody = body.length > 200 ? body.substring(0, 200) + '...' : body;
|
|
28
|
+
throw new Error(`API error: ${response.status} ${response.statusText} - ${truncatedBody}`);
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
return response.json();
|
|
31
|
+
return response.json() as T;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
// Organizations
|
|
35
|
+
async getOrganizations() {
|
|
36
|
+
return this.request<{ organizations: Organization[] }>('/organizations');
|
|
31
37
|
}
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
// Projects
|
|
40
|
+
async getProjects(organizationSlug: string) {
|
|
41
|
+
return this.request<{ projects: Project[] }>(`/organizations/${organizationSlug}/projects`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async createProject(organizationSlug: string, name: string, region: string) {
|
|
45
|
+
return this.request<{ project: Project }>(`/organizations/${organizationSlug}/projects`, {
|
|
35
46
|
method: 'POST',
|
|
36
47
|
body: JSON.stringify({ name, region }),
|
|
37
48
|
});
|
|
38
49
|
}
|
|
39
50
|
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
// Environments
|
|
52
|
+
async getEnvironments(organizationSlug: string, projectSlug: string) {
|
|
53
|
+
return this.request<{ environments: Environment[] }>(
|
|
54
|
+
`/organizations/${organizationSlug}/projects/${projectSlug}/environments`
|
|
55
|
+
);
|
|
42
56
|
}
|
|
43
57
|
|
|
44
|
-
async createEnvironment(
|
|
45
|
-
return this.request<{ environment: Environment }>(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
async createEnvironment(organizationSlug: string, projectSlug: string, name: string, region: string) {
|
|
59
|
+
return this.request<{ environment: Environment; task: Task }>(
|
|
60
|
+
`/organizations/${organizationSlug}/projects/${projectSlug}/environments`,
|
|
61
|
+
{
|
|
62
|
+
method: 'POST',
|
|
63
|
+
body: JSON.stringify({ name, region }),
|
|
64
|
+
}
|
|
65
|
+
);
|
|
49
66
|
}
|
|
50
67
|
|
|
68
|
+
async getEnvironment(organizationSlug: string, projectSlug: string, environmentSlug: string) {
|
|
69
|
+
return this.request<Environment>(
|
|
70
|
+
`/organizations/${organizationSlug}/projects/${projectSlug}/environments/${environmentSlug}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Apps
|
|
51
75
|
async registerApp(environmentId: string, appType: string, name: string) {
|
|
52
76
|
return this.request<{ app: App }>(`/environments/${environmentId}/apps`, {
|
|
53
77
|
method: 'POST',
|
|
54
78
|
body: JSON.stringify({ type: appType, name }),
|
|
55
79
|
});
|
|
56
80
|
}
|
|
81
|
+
|
|
82
|
+
// Backward compatibility methods
|
|
83
|
+
async getStores() {
|
|
84
|
+
return this.getOrganizations();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async createStore(name: string, region: string = 'us-east-1') {
|
|
88
|
+
// Create organization as "store" - requires org slug
|
|
89
|
+
// For now, create in first available org
|
|
90
|
+
const { organizations } = await this.getOrganizations();
|
|
91
|
+
if (organizations.length === 0) {
|
|
92
|
+
throw new Error('No organizations found. Create one at https://cloud.saleor.io');
|
|
93
|
+
}
|
|
94
|
+
return this.createProject(organizations[0].slug, name, region);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async createEnvironmentFromStore(storeId: string, name: string) {
|
|
98
|
+
// storeId is used as organization slug for backward compatibility
|
|
99
|
+
const { environments } = await this.getEnvironments(storeId, 'default');
|
|
100
|
+
// Create sandbox environment
|
|
101
|
+
return this.createEnvironment(storeId, environments.length > 0 ? environments[0].project?.slug || 'default' : 'default', name, 'default');
|
|
102
|
+
}
|
|
57
103
|
}
|
|
58
104
|
|
|
59
|
-
export interface
|
|
60
|
-
|
|
105
|
+
export interface Organization {
|
|
106
|
+
slug: string;
|
|
107
|
+
name: string;
|
|
108
|
+
created: string;
|
|
109
|
+
company_name?: string;
|
|
110
|
+
owner_email: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface Project {
|
|
114
|
+
slug: string;
|
|
61
115
|
name: string;
|
|
62
116
|
region: string;
|
|
63
|
-
|
|
117
|
+
created: string;
|
|
118
|
+
billing_period?: { start: string };
|
|
119
|
+
sandboxes: { count: number };
|
|
64
120
|
}
|
|
65
121
|
|
|
66
122
|
export interface Environment {
|
|
123
|
+
key: string;
|
|
124
|
+
name: string;
|
|
125
|
+
domain: string;
|
|
126
|
+
service: {
|
|
127
|
+
version: string;
|
|
128
|
+
type: string;
|
|
129
|
+
region: string;
|
|
130
|
+
};
|
|
131
|
+
created: string;
|
|
132
|
+
project: { name: string; slug: string };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface Task {
|
|
136
|
+
id: string;
|
|
137
|
+
status: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface Store {
|
|
67
141
|
id: string;
|
|
68
142
|
name: string;
|
|
69
|
-
|
|
143
|
+
region: string;
|
|
70
144
|
created_at: string;
|
|
71
145
|
}
|
|
72
146
|
|
package/src/commands/store.ts
CHANGED
|
@@ -11,8 +11,8 @@ export async function createStore(name: string, region: string): Promise<void> {
|
|
|
11
11
|
try {
|
|
12
12
|
const result = await client.createStore(name, region);
|
|
13
13
|
console.log(success(`Store created successfully!`));
|
|
14
|
-
console.log(info(`
|
|
15
|
-
console.log(info(`Dashboard: https://cloud.saleor.io/
|
|
14
|
+
console.log(info(`Project slug: ${result.project.slug}`));
|
|
15
|
+
console.log(info(`Dashboard: https://cloud.saleor.io/organizations/default/projects/${result.project.slug}`));
|
|
16
16
|
} catch (err) {
|
|
17
17
|
console.log(error(`Failed to create store: ${err}`));
|
|
18
18
|
process.exit(1);
|
|
@@ -23,21 +23,21 @@ export async function listStores(): Promise<void> {
|
|
|
23
23
|
const token = requireToken();
|
|
24
24
|
const client = new SaleorCloudClient(token);
|
|
25
25
|
|
|
26
|
-
console.log(info('Fetching
|
|
26
|
+
console.log(info('Fetching organizations...'));
|
|
27
27
|
|
|
28
28
|
try {
|
|
29
|
-
const
|
|
29
|
+
const { organizations } = await client.getOrganizations();
|
|
30
30
|
|
|
31
|
-
if (
|
|
32
|
-
console.log(info('No
|
|
31
|
+
if (organizations.length === 0) {
|
|
32
|
+
console.log(info('No organizations found. Create one at https://cloud.saleor.io'));
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
console.log(success(`Found ${
|
|
37
|
-
for (const
|
|
38
|
-
console.log(` ${
|
|
39
|
-
console.log(`
|
|
40
|
-
console.log(` Created: ${new Date(
|
|
36
|
+
console.log(success(`Found ${organizations.length} organization(s):\n`));
|
|
37
|
+
for (const org of organizations) {
|
|
38
|
+
console.log(` ${org.name} (${org.slug})`);
|
|
39
|
+
console.log(` Email: ${org.owner_email}`);
|
|
40
|
+
console.log(` Created: ${new Date(org.created).toLocaleDateString()}`);
|
|
41
41
|
console.log();
|
|
42
42
|
}
|
|
43
43
|
} catch (err) {
|
|
@@ -46,17 +46,17 @@ export async function listStores(): Promise<void> {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
export async function createEnvironment(
|
|
49
|
+
export async function createEnvironment(organizationSlug: string, name: string): Promise<void> {
|
|
50
50
|
const token = requireToken();
|
|
51
51
|
const client = new SaleorCloudClient(token);
|
|
52
52
|
|
|
53
|
-
console.log(info(`Creating environment: ${name} for
|
|
53
|
+
console.log(info(`Creating environment: ${name} for organization ${organizationSlug}...`));
|
|
54
54
|
|
|
55
55
|
try {
|
|
56
|
-
const
|
|
56
|
+
const { environment } = await client.createEnvironment(organizationSlug, 'default', name, 'us-east-1');
|
|
57
57
|
console.log(success(`Environment created successfully!`));
|
|
58
|
-
console.log(info(`Environment
|
|
59
|
-
console.log(info(`
|
|
58
|
+
console.log(info(`Environment key: ${environment.key}`));
|
|
59
|
+
console.log(info(`Domain: ${environment.domain}`));
|
|
60
60
|
} catch (err) {
|
|
61
61
|
console.log(error(`Failed to create environment: ${err}`));
|
|
62
62
|
process.exit(1);
|
|
@@ -36,67 +36,72 @@ describe('Command Handlers', () => {
|
|
|
36
36
|
describe('Store Handlers', () => {
|
|
37
37
|
Given('a valid token and successful API', () => {
|
|
38
38
|
When('calling createStore', () => {
|
|
39
|
-
Then('it should call POST /
|
|
39
|
+
Then('it should call POST /organizations/:slug/projects with name and region', async () => {
|
|
40
40
|
const fetchMock = mockFetch({
|
|
41
|
-
'/
|
|
41
|
+
'/organizations': { organizations: [fixtures.organization] },
|
|
42
|
+
'/organizations/my-org/projects': { project: fixtures.project },
|
|
42
43
|
});
|
|
43
44
|
|
|
44
45
|
const { createStore } = await import('../commands/store');
|
|
45
46
|
await createStore('my-store', 'us-east-1');
|
|
46
47
|
|
|
47
|
-
expect(fetchMock).
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
expect(
|
|
48
|
+
expect(fetchMock).toHaveBeenCalled();
|
|
49
|
+
const createCall = fetchMock.mock.calls.find(([url]: [string]) =>
|
|
50
|
+
(url as string).includes('/organizations/my-org/projects')
|
|
51
|
+
);
|
|
52
|
+
expect(createCall).toBeDefined();
|
|
53
|
+
expect(createCall?.[1].method).toBe('POST');
|
|
54
|
+
expect(JSON.parse(createCall?.[1].body)).toEqual({ name: 'my-store', region: 'us-east-1' });
|
|
52
55
|
});
|
|
53
56
|
});
|
|
54
57
|
|
|
55
58
|
When('calling listStores with results', () => {
|
|
56
|
-
Then('it should display
|
|
59
|
+
Then('it should display organization names and slugs', async () => {
|
|
57
60
|
mockFetch({
|
|
58
|
-
'/
|
|
61
|
+
'/organizations': { organizations: [fixtures.organization, fixtures.organization2] },
|
|
59
62
|
});
|
|
60
63
|
|
|
61
64
|
const { listStores } = await import('../commands/store');
|
|
62
65
|
await listStores();
|
|
63
66
|
|
|
64
67
|
const allOutput = console_.logs.join('\n');
|
|
65
|
-
expect(allOutput).toContain('
|
|
66
|
-
expect(allOutput).toContain('
|
|
67
|
-
expect(allOutput).toContain('
|
|
68
|
-
expect(allOutput).toContain('
|
|
68
|
+
expect(allOutput).toContain('My Organization');
|
|
69
|
+
expect(allOutput).toContain('my-org');
|
|
70
|
+
expect(allOutput).toContain('Other Organization');
|
|
71
|
+
expect(allOutput).toContain('other-org');
|
|
69
72
|
});
|
|
70
73
|
});
|
|
71
74
|
|
|
72
75
|
When('calling listStores with empty results', () => {
|
|
73
76
|
Then('it should display a helpful message', async () => {
|
|
74
77
|
mockFetch({
|
|
75
|
-
'/
|
|
78
|
+
'/organizations': { organizations: [] },
|
|
76
79
|
});
|
|
77
80
|
|
|
78
81
|
const { listStores } = await import('../commands/store');
|
|
79
82
|
await listStores();
|
|
80
83
|
|
|
81
84
|
const infoMessages = tuiCalls.filter(c => c.fn === 'info').map(c => c.msg).join('\n');
|
|
82
|
-
expect(infoMessages).toContain('No
|
|
85
|
+
expect(infoMessages).toContain('No organizations found');
|
|
83
86
|
});
|
|
84
87
|
});
|
|
85
88
|
|
|
86
89
|
When('calling createEnvironment', () => {
|
|
87
|
-
Then('it should call POST /
|
|
90
|
+
Then('it should call POST /organizations/:slug/projects/:slug/environments', async () => {
|
|
88
91
|
const fetchMock = mockFetch({
|
|
89
|
-
'/
|
|
92
|
+
'/organizations/my-org/projects/default/environments': { environment: fixtures.environment },
|
|
90
93
|
});
|
|
91
94
|
|
|
92
95
|
const { createEnvironment } = await import('../commands/store');
|
|
93
|
-
await createEnvironment('
|
|
94
|
-
|
|
95
|
-
expect(fetchMock).
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
expect(
|
|
96
|
+
await createEnvironment('my-org', 'staging');
|
|
97
|
+
|
|
98
|
+
expect(fetchMock).toHaveBeenCalled();
|
|
99
|
+
const createCall = fetchMock.mock.calls.find(([url]: [string]) =>
|
|
100
|
+
(url as string).includes('/organizations/my-org/projects/default/environments')
|
|
101
|
+
);
|
|
102
|
+
expect(createCall).toBeDefined();
|
|
103
|
+
expect(createCall?.[1].method).toBe('POST');
|
|
104
|
+
expect(JSON.parse(createCall?.[1].body)).toEqual({ name: 'staging', region: 'us-east-1' });
|
|
100
105
|
});
|
|
101
106
|
});
|
|
102
107
|
});
|
|
@@ -137,7 +142,7 @@ describe('Command Handlers', () => {
|
|
|
137
142
|
const { createEnvironment } = await import('../commands/store');
|
|
138
143
|
|
|
139
144
|
try {
|
|
140
|
-
await createEnvironment('bad-
|
|
145
|
+
await createEnvironment('bad-org', 'staging');
|
|
141
146
|
} catch {}
|
|
142
147
|
|
|
143
148
|
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
@@ -194,15 +199,15 @@ describe('Command Handlers', () => {
|
|
|
194
199
|
When('calling createApp', () => {
|
|
195
200
|
Then('it should register the hosted app via API', async () => {
|
|
196
201
|
const fetchMock = mockFetch({
|
|
197
|
-
'/environments/
|
|
202
|
+
'/environments/staging/apps': { app: fixtures.app },
|
|
198
203
|
});
|
|
199
204
|
|
|
200
205
|
const { createApp } = await import('../commands/app');
|
|
201
|
-
await createApp('pay-app', 'payment', '
|
|
206
|
+
await createApp('pay-app', 'payment', 'staging', 'dummy');
|
|
202
207
|
|
|
203
208
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
204
209
|
const [url, opts] = fetchMock.mock.calls[0];
|
|
205
|
-
expect(url).toContain('/environments/
|
|
210
|
+
expect(url).toContain('/environments/staging/apps');
|
|
206
211
|
expect(opts.method).toBe('POST');
|
|
207
212
|
});
|
|
208
213
|
});
|
|
@@ -216,7 +221,7 @@ describe('Command Handlers', () => {
|
|
|
216
221
|
const { createApp } = await import('../commands/app');
|
|
217
222
|
|
|
218
223
|
try {
|
|
219
|
-
await createApp('pay-app', 'payment', '
|
|
224
|
+
await createApp('pay-app', 'payment', 'staging', 'dummy');
|
|
220
225
|
} catch {}
|
|
221
226
|
|
|
222
227
|
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
@@ -36,30 +36,31 @@ describe('End-to-End Flows', () => {
|
|
|
36
36
|
describe('Store Creation Flow', () => {
|
|
37
37
|
Given('a valid token and working API', () => {
|
|
38
38
|
When('creating a store end-to-end', () => {
|
|
39
|
-
Then('it should call API and display
|
|
39
|
+
Then('it should call API and display project slug and dashboard URL', async () => {
|
|
40
40
|
const fetchMock = mockFetch({
|
|
41
|
-
'/
|
|
41
|
+
'/organizations': { organizations: [fixtures.organization] },
|
|
42
|
+
'/organizations/my-org/projects': { project: fixtures.project },
|
|
42
43
|
});
|
|
43
44
|
|
|
44
45
|
const { createStore } = await import('../commands/store');
|
|
45
46
|
await createStore('my-store', 'us-east-1');
|
|
46
47
|
|
|
47
|
-
expect(fetchMock).
|
|
48
|
+
expect(fetchMock).toHaveBeenCalled();
|
|
48
49
|
|
|
49
50
|
const allTui = tuiCalls.map(c => c.msg).join('\n');
|
|
50
|
-
expect(allTui).toContain('store
|
|
51
|
-
expect(allTui).toContain('cloud.saleor.io
|
|
51
|
+
expect(allTui).toContain('my-store');
|
|
52
|
+
expect(allTui).toContain('cloud.saleor.io');
|
|
52
53
|
});
|
|
53
54
|
});
|
|
54
55
|
});
|
|
55
56
|
});
|
|
56
57
|
|
|
57
58
|
describe('Store List Flow', () => {
|
|
58
|
-
Given('a valid token and multiple
|
|
59
|
+
Given('a valid token and multiple organizations', () => {
|
|
59
60
|
When('listing stores end-to-end', () => {
|
|
60
|
-
Then('it should display all
|
|
61
|
+
Then('it should display all organizations with emails and dates', async () => {
|
|
61
62
|
mockFetch({
|
|
62
|
-
'/
|
|
63
|
+
'/organizations': { organizations: [fixtures.organization, fixtures.organization2] },
|
|
63
64
|
});
|
|
64
65
|
|
|
65
66
|
const { listStores } = await import('../commands/store');
|
|
@@ -67,31 +68,31 @@ describe('End-to-End Flows', () => {
|
|
|
67
68
|
|
|
68
69
|
const allTui = tuiCalls.map(c => c.msg).join('\n');
|
|
69
70
|
const allConsole = console_.logs.join('\n');
|
|
70
|
-
expect(allTui).toContain('2
|
|
71
|
-
expect(allConsole).toContain('
|
|
72
|
-
expect(allConsole).toContain('
|
|
73
|
-
expect(allConsole).toContain('
|
|
74
|
-
expect(allConsole).toContain('
|
|
71
|
+
expect(allTui).toContain('2 organization(s)');
|
|
72
|
+
expect(allConsole).toContain('My Organization');
|
|
73
|
+
expect(allConsole).toContain('owner@example.com');
|
|
74
|
+
expect(allConsole).toContain('Other Organization');
|
|
75
|
+
expect(allConsole).toContain('other@example.com');
|
|
75
76
|
});
|
|
76
77
|
});
|
|
77
78
|
});
|
|
78
79
|
});
|
|
79
80
|
|
|
80
81
|
describe('Environment Creation Flow', () => {
|
|
81
|
-
Given('a valid token and existing
|
|
82
|
+
Given('a valid token and existing organization', () => {
|
|
82
83
|
When('creating an environment end-to-end', () => {
|
|
83
|
-
Then('it should call API and display environment
|
|
84
|
+
Then('it should call API and display environment key and domain', async () => {
|
|
84
85
|
const fetchMock = mockFetch({
|
|
85
|
-
'/
|
|
86
|
+
'/organizations/my-org/projects/default/environments': { environment: fixtures.environment },
|
|
86
87
|
});
|
|
87
88
|
|
|
88
89
|
const { createEnvironment } = await import('../commands/store');
|
|
89
|
-
await createEnvironment('
|
|
90
|
+
await createEnvironment('my-org', 'staging');
|
|
90
91
|
|
|
91
|
-
expect(fetchMock).
|
|
92
|
+
expect(fetchMock).toHaveBeenCalled();
|
|
92
93
|
|
|
93
94
|
const allTui = tuiCalls.map(c => c.msg).join('\n');
|
|
94
|
-
expect(allTui).toContain('
|
|
95
|
+
expect(allTui).toContain('staging');
|
|
95
96
|
expect(allTui).toContain('saleor.cloud');
|
|
96
97
|
});
|
|
97
98
|
});
|
|
@@ -103,11 +104,11 @@ describe('End-to-End Flows', () => {
|
|
|
103
104
|
When('creating a payment app with environment', () => {
|
|
104
105
|
Then('it should register via API and display app ID', async () => {
|
|
105
106
|
const fetchMock = mockFetch({
|
|
106
|
-
'/environments/
|
|
107
|
+
'/environments/staging/apps': { app: fixtures.app },
|
|
107
108
|
});
|
|
108
109
|
|
|
109
110
|
const { createApp } = await import('../commands/app');
|
|
110
|
-
await createApp('my-payment', 'payment', '
|
|
111
|
+
await createApp('my-payment', 'payment', 'staging', 'stripe');
|
|
111
112
|
|
|
112
113
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
113
114
|
|
|
@@ -138,7 +139,7 @@ describe('End-to-End Flows', () => {
|
|
|
138
139
|
When('attempting any store operation', () => {
|
|
139
140
|
Then('it should fail fast with auth error before API call', async () => {
|
|
140
141
|
withoutToken();
|
|
141
|
-
const fetchMock = mockFetch({ '/
|
|
142
|
+
const fetchMock = mockFetch({ '/organizations': { organizations: [] } });
|
|
142
143
|
|
|
143
144
|
const { listStores } = await import('../commands/store');
|
|
144
145
|
|
|
@@ -173,37 +174,55 @@ describe('End-to-End Flows', () => {
|
|
|
173
174
|
|
|
174
175
|
describe('API Client Request Construction', () => {
|
|
175
176
|
Given('a SaleorCloudClient', () => {
|
|
176
|
-
When('
|
|
177
|
+
When('listing organizations', () => {
|
|
178
|
+
Then('it should send correct URL, method, and headers', async () => {
|
|
179
|
+
const fetchMock = mockFetch({
|
|
180
|
+
'/organizations': { organizations: [fixtures.organization] },
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const { SaleorCloudClient } = await import('../api/client');
|
|
184
|
+
const client = new SaleorCloudClient(fixtures.token);
|
|
185
|
+
await client.getOrganizations();
|
|
186
|
+
|
|
187
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
188
|
+
const [url, opts] = fetchMock.mock.calls[0];
|
|
189
|
+
expect(url).toBe('https://cloud.saleor.io/api/v1/organizations');
|
|
190
|
+
expect(opts.method).toBe('GET');
|
|
191
|
+
expect(opts.headers.Authorization).toBe(`Token ${fixtures.token}`);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
When('creating a project', () => {
|
|
177
196
|
Then('it should send correct URL, method, headers, and body', async () => {
|
|
178
197
|
const fetchMock = mockFetch({
|
|
179
|
-
'/
|
|
198
|
+
'/organizations/default/projects': { project: fixtures.project },
|
|
180
199
|
});
|
|
181
200
|
|
|
182
201
|
const { SaleorCloudClient } = await import('../api/client');
|
|
183
202
|
const client = new SaleorCloudClient(fixtures.token);
|
|
184
|
-
await client.
|
|
203
|
+
await client.createProject('default', 'test-store', 'eu-west-1');
|
|
185
204
|
|
|
186
205
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
187
206
|
const [url, opts] = fetchMock.mock.calls[0];
|
|
188
|
-
expect(url).toBe('https://cloud.saleor.io/api/
|
|
207
|
+
expect(url).toBe('https://cloud.saleor.io/api/v1/organizations/default/projects');
|
|
189
208
|
expect(opts.method).toBe('POST');
|
|
190
|
-
expect(opts.headers.Authorization).toBe(`
|
|
209
|
+
expect(opts.headers.Authorization).toBe(`Token ${fixtures.token}`);
|
|
191
210
|
expect(JSON.parse(opts.body)).toEqual({ name: 'test-store', region: 'eu-west-1' });
|
|
192
211
|
});
|
|
193
212
|
});
|
|
194
213
|
|
|
195
214
|
When('registering an app', () => {
|
|
196
|
-
Then('it should send correct URL with environment
|
|
215
|
+
Then('it should send correct URL with environment key', async () => {
|
|
197
216
|
const fetchMock = mockFetch({
|
|
198
|
-
'/environments/
|
|
217
|
+
'/environments/staging/apps': { app: fixtures.app },
|
|
199
218
|
});
|
|
200
219
|
|
|
201
220
|
const { SaleorCloudClient } = await import('../api/client');
|
|
202
221
|
const client = new SaleorCloudClient(fixtures.token);
|
|
203
|
-
await client.registerApp('
|
|
222
|
+
await client.registerApp('staging', 'payment', 'my-app');
|
|
204
223
|
|
|
205
224
|
const [url, opts] = fetchMock.mock.calls[0];
|
|
206
|
-
expect(url).toBe('https://cloud.saleor.io/api/environments/
|
|
225
|
+
expect(url).toBe('https://cloud.saleor.io/api/v1/environments/staging/apps');
|
|
207
226
|
expect(JSON.parse(opts.body)).toEqual({ type: 'payment', name: 'my-app' });
|
|
208
227
|
});
|
|
209
228
|
});
|
|
@@ -50,7 +50,10 @@ describe('Entry Points', () => {
|
|
|
50
50
|
When('bootstrap calls createStore', () => {
|
|
51
51
|
Then('it should pass project name and default region', async () => {
|
|
52
52
|
withToken();
|
|
53
|
-
mockFetch({
|
|
53
|
+
mockFetch({
|
|
54
|
+
'/organizations': { organizations: [fixtures.organization] },
|
|
55
|
+
'/organizations/my-org/projects': { project: fixtures.project },
|
|
56
|
+
});
|
|
54
57
|
|
|
55
58
|
const { createStore } = await import('../commands/store');
|
|
56
59
|
await createStore('my-project', 'us-east-1');
|
|
@@ -76,7 +76,7 @@ describe('Error Handling', () => {
|
|
|
76
76
|
const { SaleorCloudClient } = await import('../api/client');
|
|
77
77
|
const client = new SaleorCloudClient(fixtures.token);
|
|
78
78
|
|
|
79
|
-
await expect(client.
|
|
79
|
+
await expect(client.getOrganizations()).rejects.toThrow('401');
|
|
80
80
|
});
|
|
81
81
|
});
|
|
82
82
|
|
|
@@ -87,7 +87,7 @@ describe('Error Handling', () => {
|
|
|
87
87
|
const { SaleorCloudClient } = await import('../api/client');
|
|
88
88
|
const client = new SaleorCloudClient(fixtures.token);
|
|
89
89
|
|
|
90
|
-
await expect(client.
|
|
90
|
+
await expect(client.createProject('test-org', 'test', 'us-east-1')).rejects.toThrow('403');
|
|
91
91
|
});
|
|
92
92
|
});
|
|
93
93
|
|
|
@@ -98,7 +98,7 @@ describe('Error Handling', () => {
|
|
|
98
98
|
const { SaleorCloudClient } = await import('../api/client');
|
|
99
99
|
const client = new SaleorCloudClient(fixtures.token);
|
|
100
100
|
|
|
101
|
-
await expect(client.getEnvironments('
|
|
101
|
+
await expect(client.getEnvironments('test-org', 'test-project')).rejects.toThrow('500');
|
|
102
102
|
});
|
|
103
103
|
});
|
|
104
104
|
|
|
@@ -109,7 +109,7 @@ describe('Error Handling', () => {
|
|
|
109
109
|
const { SaleorCloudClient } = await import('../api/client');
|
|
110
110
|
const client = new SaleorCloudClient(fixtures.token);
|
|
111
111
|
|
|
112
|
-
await expect(client.createEnvironment('bad-
|
|
112
|
+
await expect(client.createEnvironment('bad-org', 'bad-project', 'staging', 'us-east-1')).rejects.toThrow('404');
|
|
113
113
|
});
|
|
114
114
|
});
|
|
115
115
|
});
|
|
@@ -118,17 +118,17 @@ describe('Error Handling', () => {
|
|
|
118
118
|
describe('API Request Headers', () => {
|
|
119
119
|
Given('a SaleorCloudClient with a token', () => {
|
|
120
120
|
When('making any request', () => {
|
|
121
|
-
Then('it should include Authorization
|
|
121
|
+
Then('it should include Authorization Token header', async () => {
|
|
122
122
|
const fetchMock = mockFetch({
|
|
123
|
-
'/
|
|
123
|
+
'/organizations': { organizations: [] },
|
|
124
124
|
});
|
|
125
125
|
|
|
126
126
|
const { SaleorCloudClient } = await import('../api/client');
|
|
127
127
|
const client = new SaleorCloudClient(fixtures.token);
|
|
128
|
-
await client.
|
|
128
|
+
await client.getOrganizations();
|
|
129
129
|
|
|
130
130
|
const [, opts] = fetchMock.mock.calls[0];
|
|
131
|
-
expect(opts.headers.Authorization).toBe(`
|
|
131
|
+
expect(opts.headers.Authorization).toBe(`Token ${fixtures.token}`);
|
|
132
132
|
expect(opts.headers['Content-Type']).toBe('application/json');
|
|
133
133
|
});
|
|
134
134
|
});
|
package/src/test/mocks.ts
CHANGED
|
@@ -1,44 +1,84 @@
|
|
|
1
1
|
import { mock, spyOn } from 'bun:test';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Organization, Project, Environment, App } from '../api/client';
|
|
3
3
|
|
|
4
4
|
export const fixtures = {
|
|
5
5
|
token: 'test-token-abc123',
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// New API structure: Organizations → Projects → Environments
|
|
8
|
+
organization: {
|
|
9
|
+
slug: 'my-org',
|
|
10
|
+
name: 'My Organization',
|
|
11
|
+
created: '2025-01-01T00:00:00Z',
|
|
12
|
+
owner_email: 'owner@example.com',
|
|
13
|
+
company_name: 'My Company',
|
|
14
|
+
} satisfies Organization,
|
|
15
|
+
|
|
16
|
+
organization2: {
|
|
17
|
+
slug: 'other-org',
|
|
18
|
+
name: 'Other Organization',
|
|
19
|
+
created: '2025-06-15T00:00:00Z',
|
|
20
|
+
owner_email: 'other@example.com',
|
|
21
|
+
} satisfies Organization,
|
|
22
|
+
|
|
23
|
+
project: {
|
|
24
|
+
slug: 'my-store',
|
|
9
25
|
name: 'my-store',
|
|
10
26
|
region: 'us-east-1',
|
|
11
|
-
|
|
12
|
-
|
|
27
|
+
created: '2025-01-01T00:00:00Z',
|
|
28
|
+
sandboxes: { count: 2 },
|
|
29
|
+
} satisfies Project,
|
|
13
30
|
|
|
14
|
-
|
|
15
|
-
|
|
31
|
+
project2: {
|
|
32
|
+
slug: 'other-store',
|
|
16
33
|
name: 'other-store',
|
|
17
34
|
region: 'eu-west-1',
|
|
18
|
-
|
|
19
|
-
|
|
35
|
+
created: '2025-06-15T00:00:00Z',
|
|
36
|
+
sandboxes: { count: 1 },
|
|
37
|
+
} satisfies Project,
|
|
20
38
|
|
|
21
39
|
environment: {
|
|
22
|
-
|
|
40
|
+
key: 'staging',
|
|
23
41
|
name: 'staging',
|
|
24
|
-
|
|
25
|
-
|
|
42
|
+
domain: 'staging-my-store.saleor.cloud',
|
|
43
|
+
service: {
|
|
44
|
+
version: '3.15.0',
|
|
45
|
+
type: 'main',
|
|
46
|
+
region: 'us-east-1',
|
|
47
|
+
},
|
|
48
|
+
created: '2025-01-02T00:00:00Z',
|
|
49
|
+
project: { name: 'my-store', slug: 'my-store' },
|
|
26
50
|
} satisfies Environment,
|
|
27
51
|
|
|
28
52
|
app: {
|
|
29
53
|
id: 'app-1',
|
|
30
54
|
name: 'my-app',
|
|
31
55
|
type: 'payment',
|
|
32
|
-
environment_id: '
|
|
56
|
+
environment_id: 'staging',
|
|
33
57
|
} satisfies App,
|
|
58
|
+
|
|
59
|
+
// Backward compat aliases (for tests that haven't been migrated yet)
|
|
60
|
+
store: {
|
|
61
|
+
id: 'my-org',
|
|
62
|
+
name: 'My Organization',
|
|
63
|
+
region: 'us-east-1',
|
|
64
|
+
created_at: '2025-01-01T00:00:00Z',
|
|
65
|
+
},
|
|
66
|
+
store2: {
|
|
67
|
+
id: 'other-org',
|
|
68
|
+
name: 'Other Organization',
|
|
69
|
+
region: 'eu-west-1',
|
|
70
|
+
created_at: '2025-06-15T00:00:00Z',
|
|
71
|
+
},
|
|
34
72
|
};
|
|
35
73
|
|
|
36
74
|
export function mockFetch(routes: Record<string, unknown>): ReturnType<typeof mock> {
|
|
37
75
|
const handler = mock((url: string | URL | Request, init?: RequestInit) => {
|
|
38
76
|
const urlStr = typeof url === 'string' ? url : url instanceof URL ? url.toString() : url.url;
|
|
39
77
|
|
|
40
|
-
|
|
41
|
-
|
|
78
|
+
const sortedEntries = Object.entries(routes).sort((a, b) => b[0].length - a[0].length);
|
|
79
|
+
|
|
80
|
+
for (const [pattern, body] of sortedEntries) {
|
|
81
|
+
if (urlStr.endsWith(pattern)) {
|
|
42
82
|
return Promise.resolve(new Response(JSON.stringify(body), {
|
|
43
83
|
status: 200,
|
|
44
84
|
headers: { 'Content-Type': 'application/json' },
|