@google-cloud/cloud-run-mcp 1.10.0 → 1.11.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/README.md +24 -0
- package/SECURITY.md +7 -0
- package/cloudbuild.yaml +37 -0
- package/example-sources-to-deploy/java/pom.xml +41 -0
- package/example-sources-to-deploy/java/src/main/java/com/example/helloworld/HelloworldApplication.java +21 -0
- package/example-sources-to-deploy/java/src/main/resources/application.properties +1 -0
- package/example-sources-to-deploy/nodejs/index.js +15 -0
- package/example-sources-to-deploy/nodejs/package.json +12 -0
- package/example-sources-to-deploy/python/pip-project/main.py +11 -0
- package/example-sources-to-deploy/python/pip-project/requirements.txt +2 -0
- package/example-sources-to-deploy/python/pyproject-project/main.py +11 -0
- package/example-sources-to-deploy/python/pyproject-project/pyproject.toml +10 -0
- package/lib/clients.js +104 -3
- package/lib/cloud-api/build.js +192 -28
- package/lib/cloud-api/run.js +62 -0
- package/lib/cloud-api/secrets.js +196 -0
- package/lib/cloud-api/storage.js +164 -16
- package/lib/deployment/compose.js +517 -0
- package/lib/deployment/constants.js +8 -0
- package/lib/deployment/deployer.js +283 -28
- package/lib/deployment/helpers.js +58 -1
- package/lib/deployment/universal-maker.js +21 -148
- package/lib/util/archive.js +3 -3
- package/lib/util/artifacts.js +189 -0
- package/lib/util/helpers.js +109 -0
- package/mcp-server.js +4 -0
- package/package.json +6 -3
- package/skills/cloud-run/SKILL.md +109 -0
- package/test/local/archive.test.js +88 -0
- package/test/local/clients.test.js +224 -2
- package/test/local/cloud-api/build.test.js +330 -25
- package/test/local/cloud-api/run.test.js +129 -0
- package/test/local/cloud-api/secrets.test.js +291 -0
- package/test/local/cloud-api/storage.test.js +405 -0
- package/test/local/compose.test.js +618 -0
- package/test/local/deploy.test.js +408 -0
- package/test/local/deployment-helpers.test.js +100 -1
- package/test/local/test-utils.js +2 -18
- package/test/local/tools.test.js +43 -1
- package/test/local/universal-maker.test.js +23 -114
- package/test/local/util.test.js +361 -0
- package/test/need-gcp/deployer.test.js +1 -1
- package/test/need-gcp/workflows/deployment-workflows.test.js +172 -6
- package/tools/register-tools.js +19 -3
- /package/example-sources-to-deploy/{Dockerfile → golang/Dockerfile} +0 -0
- /package/example-sources-to-deploy/{go.mod → golang/go.mod} +0 -0
- /package/example-sources-to-deploy/{main.go → golang/main.go} +0 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cloud-run
|
|
3
|
+
description: 'Manage Cloud Run services and jobs.'
|
|
4
|
+
metadata:
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
openclaw:
|
|
7
|
+
category: 'compute'
|
|
8
|
+
requires:
|
|
9
|
+
bins:
|
|
10
|
+
- gcloud
|
|
11
|
+
cliHelp: 'gcloud run --help'
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# run (v1)
|
|
15
|
+
|
|
16
|
+
> **PREREQUISITE:** Ensure you have the `gcloud` CLI installed and authenticated with `gcloud auth login`. Set your project with `gcloud config set project [PROJECT_ID]`.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
gcloud run <resource> <method> [flags]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Helper Commands
|
|
23
|
+
|
|
24
|
+
| Command | Description |
|
|
25
|
+
| -------- | -------------------------------------------- |
|
|
26
|
+
| `deploy` | Create or update a Cloud Run service or job. |
|
|
27
|
+
|
|
28
|
+
## API Resources
|
|
29
|
+
|
|
30
|
+
### services
|
|
31
|
+
|
|
32
|
+
- `list` — List available services in the specified region.
|
|
33
|
+
- `describe` — Obtain details about a given service, such as its URL and configuration.
|
|
34
|
+
- `update` — Update Cloud Run environment variables, concurrency settings, and other configuration.
|
|
35
|
+
- `delete` — Delete a service and its associated revisions.
|
|
36
|
+
- `update-traffic` — Adjust the traffic assignments for a Cloud Run service.
|
|
37
|
+
- `proxy` — Proxy a service to localhost authenticating as the active account.
|
|
38
|
+
- `logs read` — Read logs for a Cloud Run service.
|
|
39
|
+
- `replace` - Create or replace a service from a YAML service specification.
|
|
40
|
+
- `add-iam-policy-binding` — Add IAM policy binding to a Cloud Run service (e.g., to make it public).
|
|
41
|
+
- `remove-iam-policy-binding` - Remove IAM policy binding of a Cloud Run service.
|
|
42
|
+
- `get-iam-policy` - Get the IAM policy for a Cloud Run service.
|
|
43
|
+
- `set-iam-policy` - Set the IAM policy for a Cloud Run service.
|
|
44
|
+
|
|
45
|
+
### jobs
|
|
46
|
+
|
|
47
|
+
- `create` — Create a Cloud Run job.
|
|
48
|
+
- `execute` — Start an execution of a Cloud Run job.
|
|
49
|
+
- `list` — List available jobs.
|
|
50
|
+
- `describe` — Obtain details about a given job.
|
|
51
|
+
- `deploy` - Create or update a Cloud Run job
|
|
52
|
+
- `update` — Update a Cloud Run job configuration.
|
|
53
|
+
- `delete` — Delete a Cloud Run job.
|
|
54
|
+
- `replace` - Create or replace a Cloud Run job from a YAML job specification.
|
|
55
|
+
- `executions list` — List executions of a Cloud Run job.
|
|
56
|
+
- `logs read` — Read logs for a Cloud Run job.
|
|
57
|
+
- `add-iam-policy-binding` — Add IAM policy binding to a Cloud Run job.
|
|
58
|
+
- `remove-iam-policy-binding` - Remove IAM policy binding of a Cloud Run job.
|
|
59
|
+
- `get-iam-policy` - Get the IAM policy for a Cloud Run job.
|
|
60
|
+
- `set-iam-policy` - Set the IAM policy for a Cloud Run job.
|
|
61
|
+
|
|
62
|
+
### domain-mappings
|
|
63
|
+
|
|
64
|
+
- `list` — List domain mappings.
|
|
65
|
+
- `create` — Create a new domain mapping.
|
|
66
|
+
- `describe` — Obtain details about a domain mapping.
|
|
67
|
+
- `delete` — Delete a domain mapping.
|
|
68
|
+
|
|
69
|
+
### multi-region-services
|
|
70
|
+
|
|
71
|
+
- `list` — List multi-region services.
|
|
72
|
+
- `describe` — Obtain details about a multi-region service.
|
|
73
|
+
- `update` — Update settings for multi-region services.
|
|
74
|
+
- `delete` — Delete a multi-region service.
|
|
75
|
+
- `replace` - Create or Update multi-region service from YAML specification.
|
|
76
|
+
|
|
77
|
+
### revisions
|
|
78
|
+
|
|
79
|
+
- `list` — List available revisions for a service.
|
|
80
|
+
- `describe` — Obtain details about a specific revision.
|
|
81
|
+
- `delete` — Delete a specific revision.
|
|
82
|
+
|
|
83
|
+
### regions
|
|
84
|
+
|
|
85
|
+
- `list` — View available Cloud Run (fully managed) regions.
|
|
86
|
+
|
|
87
|
+
### compose
|
|
88
|
+
|
|
89
|
+
- `up` — Deploy to Cloud Run from a compose specification.
|
|
90
|
+
|
|
91
|
+
## Discovering Commands
|
|
92
|
+
|
|
93
|
+
Before calling any command, inspect it for help:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Browse resources and methods
|
|
97
|
+
gcloud run --help
|
|
98
|
+
|
|
99
|
+
# Inspect a specific resource methods
|
|
100
|
+
gcloud run services --help
|
|
101
|
+
|
|
102
|
+
# Inspect a specific resource's sub-group methods
|
|
103
|
+
gcloud run jobs executions --help
|
|
104
|
+
|
|
105
|
+
# Inspect a method's specific flags and arguments
|
|
106
|
+
gcloud run deploy --help
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Use the output of `--help` to discover available flags like `--image`, `--env-vars`, `--memory`, etc.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Google LLC
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { test, describe, beforeEach, afterEach } from 'node:test';
|
|
18
|
+
import assert from 'node:assert/strict';
|
|
19
|
+
import fs from 'fs';
|
|
20
|
+
import path from 'path';
|
|
21
|
+
import os from 'os';
|
|
22
|
+
import { zipFiles } from '../../lib/util/archive.js';
|
|
23
|
+
|
|
24
|
+
describe('Archive Utility (zipFiles)', () => {
|
|
25
|
+
let tempDir;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'archive-test-'));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
if (tempDir && fs.existsSync(tempDir)) {
|
|
33
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('creates a valid zip archive in memory from buffer objects', async () => {
|
|
38
|
+
const files = [
|
|
39
|
+
{ filename: 'file1.txt', content: 'Hello World' },
|
|
40
|
+
{ filename: 'file2.json', content: JSON.stringify({ key: 'value' }) },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const zipBuffer = await zipFiles(files, false);
|
|
44
|
+
assert.ok(Buffer.isBuffer(zipBuffer));
|
|
45
|
+
assert.ok(zipBuffer.length > 0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('creates a valid tar.gz archive in memory from buffer objects', async () => {
|
|
49
|
+
const files = [{ filename: 'file1.txt', content: 'Hello World Tar' }];
|
|
50
|
+
|
|
51
|
+
const tarBuffer = await zipFiles(files, true);
|
|
52
|
+
assert.ok(Buffer.isBuffer(tarBuffer));
|
|
53
|
+
assert.ok(tarBuffer.length > 0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('creates archive from actual file paths on disk', async () => {
|
|
57
|
+
const testFilePath = path.join(tempDir, 'sample.txt');
|
|
58
|
+
fs.writeFileSync(testFilePath, 'Sample file content');
|
|
59
|
+
|
|
60
|
+
const zipBuffer = await zipFiles([testFilePath], false);
|
|
61
|
+
assert.ok(Buffer.isBuffer(zipBuffer));
|
|
62
|
+
assert.ok(zipBuffer.length > 0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('creates archive from directory path on disk', async () => {
|
|
66
|
+
const subDir = path.join(tempDir, 'subdir');
|
|
67
|
+
fs.mkdirSync(subDir);
|
|
68
|
+
fs.writeFileSync(path.join(subDir, 'nested.txt'), 'Nested content');
|
|
69
|
+
|
|
70
|
+
const zipBuffer = await zipFiles([subDir], false);
|
|
71
|
+
assert.ok(Buffer.isBuffer(zipBuffer));
|
|
72
|
+
assert.ok(zipBuffer.length > 0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('throws error if file path does not exist', async () => {
|
|
76
|
+
const nonExistentPath = path.join(tempDir, 'does-not-exist.txt');
|
|
77
|
+
await assert.rejects(
|
|
78
|
+
async () => {
|
|
79
|
+
await zipFiles([nonExistentPath], false);
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
message: new RegExp(
|
|
83
|
+
`File or directory not found: ${nonExistentPath.replace(/\\/g, '\\\\')}`
|
|
84
|
+
),
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import { test, describe } from 'node:test';
|
|
1
|
+
import { test, describe, mock, beforeEach } from 'node:test';
|
|
2
2
|
import assert from 'node:assert';
|
|
3
|
-
import {
|
|
3
|
+
import { google } from 'googleapis';
|
|
4
|
+
import {
|
|
5
|
+
clients,
|
|
6
|
+
getClient,
|
|
7
|
+
getRunV1Client,
|
|
8
|
+
getCloudRunRegions,
|
|
9
|
+
resetCachedRegions,
|
|
10
|
+
} from '../../lib/clients.js';
|
|
11
|
+
import { GCLOUD_AUTH } from '../../constants.js';
|
|
4
12
|
|
|
5
13
|
describe('getClient Helper', () => {
|
|
6
14
|
class MockClient {
|
|
@@ -174,3 +182,217 @@ describe('getClient Helper', () => {
|
|
|
174
182
|
assert.strictEqual(client.options.extraOpt, extraOpt);
|
|
175
183
|
});
|
|
176
184
|
});
|
|
185
|
+
|
|
186
|
+
describe('getRunV1Client', () => {
|
|
187
|
+
test('creates new client instance with Application Default Credentials for GCLOUD_AUTH', async () => {
|
|
188
|
+
// Mock the GoogleAuth class to avoid looking up ADC
|
|
189
|
+
const originalGoogleAuth = google.auth.GoogleAuth;
|
|
190
|
+
const authInstances = [];
|
|
191
|
+
google.auth.GoogleAuth = class MockGoogleAuth {
|
|
192
|
+
constructor(options) {
|
|
193
|
+
this.options = options;
|
|
194
|
+
authInstances.push(this);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const runMock = mock.method(google, 'run', (options) => {
|
|
199
|
+
return { options };
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const projectId = 'test-project';
|
|
204
|
+
const client = await getRunV1Client(projectId, GCLOUD_AUTH);
|
|
205
|
+
|
|
206
|
+
assert.ok(client);
|
|
207
|
+
assert.strictEqual(authInstances.length, 1);
|
|
208
|
+
assert.deepEqual(authInstances[0].options.scopes, [
|
|
209
|
+
'https://www.googleapis.com/auth/cloud-platform',
|
|
210
|
+
]);
|
|
211
|
+
assert.strictEqual(runMock.mock.calls.length, 1);
|
|
212
|
+
assert.strictEqual(
|
|
213
|
+
runMock.mock.calls[0].arguments[0].auth,
|
|
214
|
+
authInstances[0]
|
|
215
|
+
);
|
|
216
|
+
} finally {
|
|
217
|
+
google.auth.GoogleAuth = originalGoogleAuth;
|
|
218
|
+
runMock.mock.restore();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('creates new client instance with OAuth client when access token provided', async () => {
|
|
223
|
+
const runMock = mock.method(google, 'run', (options) => ({ options }));
|
|
224
|
+
try {
|
|
225
|
+
const projectId = 'test-project-oauth';
|
|
226
|
+
const accessToken = 'fake-access-token';
|
|
227
|
+
const client = await getRunV1Client(projectId, accessToken);
|
|
228
|
+
|
|
229
|
+
assert.ok(client);
|
|
230
|
+
assert.strictEqual(runMock.mock.calls.length, 1);
|
|
231
|
+
const lastCall = runMock.mock.calls[0];
|
|
232
|
+
assert.ok(lastCall.arguments[0].auth);
|
|
233
|
+
assert.strictEqual(
|
|
234
|
+
lastCall.arguments[0].auth.credentials.access_token,
|
|
235
|
+
accessToken
|
|
236
|
+
);
|
|
237
|
+
} finally {
|
|
238
|
+
runMock.mock.restore();
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('sets rootUrl if region is provided', async () => {
|
|
243
|
+
const originalGoogleAuth = google.auth.GoogleAuth;
|
|
244
|
+
google.auth.GoogleAuth = class MockGoogleAuth {
|
|
245
|
+
constructor(options) {
|
|
246
|
+
this.options = options;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const runMock = mock.method(google, 'run', (options) => ({ options }));
|
|
251
|
+
|
|
252
|
+
const projectId = 'test-project-region';
|
|
253
|
+
const region = 'us-central1';
|
|
254
|
+
const key = projectId; // If GCLOUD_AUTH, key is just projectId
|
|
255
|
+
|
|
256
|
+
// Mock getCloudRunRegions call by injecting into clients.run
|
|
257
|
+
clients.run.set(key, {
|
|
258
|
+
async *listLocationsAsync() {
|
|
259
|
+
yield { locationId: 'us-central1' };
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
await getRunV1Client(projectId, GCLOUD_AUTH, region);
|
|
265
|
+
|
|
266
|
+
const lastCall = runMock.mock.calls[0];
|
|
267
|
+
assert.strictEqual(
|
|
268
|
+
lastCall.arguments[0].rootUrl,
|
|
269
|
+
'https://us-central1-run.googleapis.com/'
|
|
270
|
+
);
|
|
271
|
+
} finally {
|
|
272
|
+
google.auth.GoogleAuth = originalGoogleAuth;
|
|
273
|
+
runMock.mock.restore();
|
|
274
|
+
clients.run.delete(key);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test('caches client instances by key', async () => {
|
|
279
|
+
const runMock = mock.method(google, 'run', (options) => ({ options }));
|
|
280
|
+
try {
|
|
281
|
+
const projectId = 'test-project-cache';
|
|
282
|
+
const client1 = await getRunV1Client(projectId, GCLOUD_AUTH);
|
|
283
|
+
const client2 = await getRunV1Client(projectId, GCLOUD_AUTH);
|
|
284
|
+
|
|
285
|
+
assert.strictEqual(client1, client2);
|
|
286
|
+
assert.strictEqual(runMock.mock.calls.length, 1);
|
|
287
|
+
} finally {
|
|
288
|
+
runMock.mock.restore();
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test('caches client instances separately for different regions', async () => {
|
|
293
|
+
resetCachedRegions();
|
|
294
|
+
const runMock = mock.method(google, 'run', (options) => ({ options }));
|
|
295
|
+
try {
|
|
296
|
+
const projectId = 'test-project-region-cache';
|
|
297
|
+
const region1 = 'us-central1';
|
|
298
|
+
const region2 = 'europe-west1';
|
|
299
|
+
|
|
300
|
+
// Mock getCloudRunRegions for both regions
|
|
301
|
+
const key = projectId; // GCLOUD_AUTH key for getRunClient/getCloudRunRegions
|
|
302
|
+
clients.run.set(key, {
|
|
303
|
+
async *listLocationsAsync() {
|
|
304
|
+
yield { locationId: region1 };
|
|
305
|
+
yield { locationId: region2 };
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const client1 = await getRunV1Client(projectId, GCLOUD_AUTH, region1);
|
|
310
|
+
const client2 = await getRunV1Client(projectId, GCLOUD_AUTH, region2);
|
|
311
|
+
|
|
312
|
+
assert.notStrictEqual(client1, client2);
|
|
313
|
+
assert.strictEqual(runMock.mock.calls.length, 2);
|
|
314
|
+
assert.strictEqual(
|
|
315
|
+
runMock.mock.calls[0].arguments[0].rootUrl,
|
|
316
|
+
`https://${region1}-run.googleapis.com/`
|
|
317
|
+
);
|
|
318
|
+
assert.strictEqual(
|
|
319
|
+
runMock.mock.calls[1].arguments[0].rootUrl,
|
|
320
|
+
`https://${region2}-run.googleapis.com/`
|
|
321
|
+
);
|
|
322
|
+
} finally {
|
|
323
|
+
runMock.mock.restore();
|
|
324
|
+
clients.run.clear();
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
describe('getCloudRunRegions', () => {
|
|
330
|
+
beforeEach(() => {
|
|
331
|
+
resetCachedRegions();
|
|
332
|
+
// Clear the run map to ensure isolation between tests
|
|
333
|
+
clients.run.clear();
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test('returns list of regions using a mock injected into the clients cache', async () => {
|
|
337
|
+
const projectId = 'test-project-1';
|
|
338
|
+
const accessToken = 'token-1';
|
|
339
|
+
// Match the key generation logic
|
|
340
|
+
const key = projectId + accessToken;
|
|
341
|
+
|
|
342
|
+
const mockRunClient = {
|
|
343
|
+
async *listLocationsAsync() {
|
|
344
|
+
yield { locationId: 'us-central1' };
|
|
345
|
+
yield { locationId: 'europe-west1' };
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
clients.run.set(key, mockRunClient);
|
|
350
|
+
|
|
351
|
+
const regions = await getCloudRunRegions(projectId, accessToken);
|
|
352
|
+
assert.deepStrictEqual(regions, ['us-central1', 'europe-west1']);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test('caches the regions after first call', async () => {
|
|
356
|
+
const projectId = 'test-project-2';
|
|
357
|
+
const accessToken = 'token-2';
|
|
358
|
+
const key = projectId + accessToken;
|
|
359
|
+
|
|
360
|
+
let callCount = 0;
|
|
361
|
+
const mockRunClient = {
|
|
362
|
+
async *listLocationsAsync() {
|
|
363
|
+
callCount++;
|
|
364
|
+
yield { locationId: 'us-central1' };
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
clients.run.set(key, mockRunClient);
|
|
369
|
+
|
|
370
|
+
await getCloudRunRegions(projectId, accessToken);
|
|
371
|
+
const regions = await getCloudRunRegions(projectId, accessToken);
|
|
372
|
+
|
|
373
|
+
assert.strictEqual(callCount, 1);
|
|
374
|
+
assert.deepStrictEqual(regions, ['us-central1']);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test('using different accessToken allows isolated client mocks', async () => {
|
|
378
|
+
const projectId = 'test-project-3';
|
|
379
|
+
|
|
380
|
+
clients.run.set(projectId + 'token-a', {
|
|
381
|
+
async *listLocationsAsync() {
|
|
382
|
+
yield { locationId: 'region-a' };
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
clients.run.set(projectId + 'token-b', {
|
|
386
|
+
async *listLocationsAsync() {
|
|
387
|
+
yield { locationId: 'region-b' };
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const resA = await getCloudRunRegions(projectId, 'token-a');
|
|
392
|
+
resetCachedRegions();
|
|
393
|
+
const resB = await getCloudRunRegions(projectId, 'token-b');
|
|
394
|
+
|
|
395
|
+
assert.deepStrictEqual(resA, ['region-a']);
|
|
396
|
+
assert.deepStrictEqual(resB, ['region-b']);
|
|
397
|
+
});
|
|
398
|
+
});
|