@google-cloud/cloud-run-mcp 1.7.0 → 1.9.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.
@@ -1,50 +1,111 @@
1
1
  import assert from 'node:assert/strict';
2
- import { describe, it, mock } from 'node:test';
2
+ import { describe, it, mock, beforeEach, afterEach } from 'node:test';
3
3
  import esmock from 'esmock';
4
4
 
5
5
  describe('triggerCloudBuild', () => {
6
- it('should return a successful build and log correct messages', async () => {
7
- const mockBuildId = 'mock-build-id';
8
- const mockSuccessResult = {
9
- id: mockBuildId,
10
- status: 'SUCCESS',
11
- results: { images: [{ name: 'gcr.io/mock-project/mock-image' }] },
12
- };
13
-
14
- const getBuildMock = mock.fn(() => Promise.resolve([mockSuccessResult]));
15
- const logAndProgressMock = mock.fn();
16
-
17
- const { triggerCloudBuild } = await esmock(
18
- '../../../lib/cloud-api/build.js',
19
- {
20
- '../../../lib/cloud-api/helpers.js': {
21
- callWithRetry: (fn) => fn(), // Directly execute the function
22
- },
23
- '../../../lib/util/helpers.js': {
24
- logAndProgress: logAndProgressMock,
25
- },
26
- }
6
+ const mockBuildId = 'mock-build-id';
7
+ // bW9jay1idWlsZC1pZA== is Buffer.from('mock-build-id').toString('base64')
8
+ const base64BuildId = 'bW9jay1idWlsZC1pZA==';
9
+ const goodSubmitBuildResponse = [
10
+ {
11
+ buildOperation: {
12
+ name: `projects/mock-project/locations/mock-location/operations/${base64BuildId}`,
13
+ },
14
+ },
15
+ ];
16
+ const mockSuccessResult = {
17
+ id: mockBuildId,
18
+ status: 'SUCCESS',
19
+ results: { images: [{ name: 'gcr.io/mock-project/mock-image' }] },
20
+ };
21
+ const mockFailureResult = {
22
+ id: mockBuildId,
23
+ status: 'FAILURE',
24
+ logUrl: 'http://mock-log-url.com',
25
+ results: { images: [{ name: 'gcr.io/mock-project/mock-image' }] },
26
+ };
27
+
28
+ let logAndProgressMock;
29
+ let getServiceMock;
30
+ let createServiceMock;
31
+ let updateServiceMock;
32
+ let servicePathMock;
33
+ let locationPathMock;
34
+ let submitBuildMock;
35
+ let getBuildMock;
36
+ let getEntriesMock;
37
+ let context;
38
+ let setTimeoutMock;
39
+ let checkServiceMock;
40
+
41
+ beforeEach(() => {
42
+ logAndProgressMock = mock.fn();
43
+ checkServiceMock = mock.fn(() => Promise.resolve(false));
44
+ getServiceMock = mock.fn(() => Promise.reject({ code: 5 })); // Default: service not found
45
+ createServiceMock = mock.fn(() => Promise.resolve());
46
+ updateServiceMock = mock.fn(() => Promise.resolve());
47
+ servicePathMock = mock.fn(
48
+ (projectId, location, serviceId) =>
49
+ `projects/${projectId}/locations/${location}/services/${serviceId}`
27
50
  );
51
+ locationPathMock = mock.fn(
52
+ (projectId, location) => `projects/${projectId}/locations/${location}`
53
+ );
54
+ submitBuildMock = mock.fn(() => Promise.resolve(goodSubmitBuildResponse));
55
+ getBuildMock = mock.fn(() => Promise.resolve([mockSuccessResult]));
56
+ getEntriesMock = mock.fn(() =>
57
+ Promise.resolve([[{ data: 'log line 1' }, { data: 'log line 2' }]])
58
+ );
59
+ setTimeoutMock = mock.fn((cb) => cb());
60
+ mock.method(global, 'setTimeout', setTimeoutMock);
28
61
 
29
- const context = {
62
+ context = {
63
+ runClient: {
64
+ getService: getServiceMock,
65
+ createService: createServiceMock,
66
+ updateService: updateServiceMock,
67
+ servicePath: servicePathMock,
68
+ locationPath: locationPathMock,
69
+ },
70
+ buildsClient: {
71
+ submitBuild: submitBuildMock,
72
+ },
30
73
  cloudBuildClient: {
31
- createBuild: mock.fn(() =>
32
- Promise.resolve([
33
- {
34
- metadata: {
35
- build: {
36
- id: mockBuildId,
37
- },
38
- },
39
- },
40
- ])
41
- ),
42
74
  getBuild: getBuildMock,
43
75
  },
76
+ loggingClient: {
77
+ getEntries: getEntriesMock,
78
+ },
44
79
  };
80
+ });
45
81
 
82
+ afterEach(() => {
83
+ mock.restoreAll();
84
+ });
85
+
86
+ async function getTriggerCloudBuild() {
87
+ return await esmock('../../../lib/cloud-api/build.js', {
88
+ '../../../lib/cloud-api/helpers.js': {
89
+ callWithRetry: (fn) => fn(),
90
+ },
91
+ '../../../lib/util/helpers.js': {
92
+ logAndProgress: logAndProgressMock,
93
+ },
94
+ '../../../lib/cloud-api/run.js': {
95
+ checkCloudRunServiceExists: checkServiceMock,
96
+ },
97
+ '../../../lib/clients.js': {
98
+ getRunClient: () => Promise.resolve(context.runClient),
99
+ getBuildsClient: () => Promise.resolve(context.buildsClient),
100
+ getCloudBuildClient: () => Promise.resolve(context.cloudBuildClient),
101
+ getLoggingClient: () => Promise.resolve(context.loggingClient),
102
+ },
103
+ });
104
+ }
105
+
106
+ it('should run successfully and create service when service does not exist', async () => {
107
+ const { triggerCloudBuild } = await getTriggerCloudBuild();
46
108
  const result = await triggerCloudBuild(
47
- context,
48
109
  'mock-project',
49
110
  'mock-location',
50
111
  'mock-bucket',
@@ -56,216 +117,97 @@ describe('triggerCloudBuild', () => {
56
117
  );
57
118
 
58
119
  assert.deepStrictEqual(result, mockSuccessResult);
59
- assert.strictEqual(
60
- context.cloudBuildClient.createBuild.mock.callCount(),
61
- 1
62
- );
63
- assert.strictEqual(context.cloudBuildClient.getBuild.mock.callCount(), 1);
120
+ assert.strictEqual(checkServiceMock.mock.callCount(), 1);
121
+ assert.strictEqual(submitBuildMock.mock.callCount(), 1);
122
+ assert.strictEqual(getBuildMock.mock.callCount(), 1);
123
+ assert.strictEqual(createServiceMock.mock.callCount(), 1); // 1 dry run
124
+ assert.strictEqual(updateServiceMock.mock.callCount(), 0);
64
125
 
65
126
  const { calls: logCalls } = logAndProgressMock.mock;
66
- assert.match(logCalls[0].arguments[0], /Initiating Cloud Build/);
67
- assert.match(logCalls[1].arguments[0], /Cloud Build job started/);
68
- assert.match(logCalls[2].arguments[0], /completed successfully/);
69
- assert.match(logCalls[3].arguments[0], /Image built/);
127
+ assert.match(logCalls[0].arguments[0], /Performing dry-run creation/);
128
+ assert.match(logCalls[1].arguments[0], /Dry-run validation successful/);
129
+ assert.match(logCalls[2].arguments[0], /Initiating Cloud Build/);
130
+ assert.match(logCalls[3].arguments[0], /Cloud Build job started/);
131
+ assert.match(logCalls[4].arguments[0], /completed successfully/);
132
+ assert.match(logCalls[5].arguments[0], /Image built/);
70
133
  });
71
134
 
72
- it('should throw an error for a failed build and log correct messages', async () => {
73
- const mockBuildId = 'mock-build-id-failure';
74
- const mockFailureResult = {
75
- id: mockBuildId,
76
- status: 'FAILURE',
77
- logUrl: 'http://mock-log-url.com',
78
- };
79
-
80
- const getBuildMock = mock.fn(() => Promise.resolve([mockFailureResult]));
81
- const logAndProgressMock = mock.fn();
82
- const setTimeoutMock = mock.fn((resolve) => resolve());
83
- mock.method(global, 'setTimeout', setTimeoutMock);
84
-
85
- const { triggerCloudBuild } = await esmock(
86
- '../../../lib/cloud-api/build.js',
87
- {
88
- '../../../lib/cloud-api/helpers.js': {
89
- callWithRetry: (fn) => fn(),
90
- },
91
- '../../../lib/util/helpers.js': {
92
- logAndProgress: logAndProgressMock,
93
- },
94
- }
95
- );
96
-
97
- const context = {
98
- cloudBuildClient: {
99
- createBuild: mock.fn(() =>
100
- Promise.resolve([
101
- {
102
- metadata: {
103
- build: {
104
- id: mockBuildId,
105
- },
106
- },
107
- },
108
- ])
109
- ),
110
- getBuild: getBuildMock,
111
- },
112
- loggingClient: {
113
- getEntries: mock.fn(() =>
114
- Promise.resolve([[{ data: 'log line 1' }, { data: 'log line 2' }]])
115
- ),
116
- },
117
- };
118
-
119
- await assert.rejects(
120
- () =>
121
- triggerCloudBuild(
122
- context,
123
- 'mock-project',
124
- 'mock-location',
125
- 'mock-bucket',
126
- 'mock-blob',
127
- 'mock-repo',
128
- 'gcr.io/mock-project/mock-image',
129
- true,
130
- () => {}
131
- ),
132
- (err) => {
133
- assert.match(err.message, /Build mock-build-id-failure failed/);
134
- assert.match(err.message, /log line 1/);
135
- assert.match(err.message, /log line 2/);
136
- return true;
137
- }
138
- );
139
-
140
- assert.strictEqual(
141
- context.cloudBuildClient.createBuild.mock.callCount(),
142
- 1
135
+ it('should run successfully and update service when service exists', async () => {
136
+ checkServiceMock.mock.mockImplementation(() => Promise.resolve(true));
137
+ const { triggerCloudBuild } = await getTriggerCloudBuild();
138
+ await triggerCloudBuild(
139
+ 'mock-project',
140
+ 'mock-location',
141
+ 'mock-bucket',
142
+ 'mock-blob',
143
+ 'mock-repo',
144
+ 'gcr.io/mock-project/mock-image',
145
+ true,
146
+ () => {}
143
147
  );
144
- assert.strictEqual(context.cloudBuildClient.getBuild.mock.callCount(), 1);
145
- assert.strictEqual(context.loggingClient.getEntries.mock.callCount(), 1);
146
- assert.strictEqual(setTimeoutMock.mock.callCount(), 1);
147
- assert.strictEqual(setTimeoutMock.mock.calls[0].arguments[1], 10000);
148
148
 
149
+ assert.strictEqual(checkServiceMock.mock.callCount(), 1);
150
+ assert.strictEqual(submitBuildMock.mock.callCount(), 1);
151
+ assert.strictEqual(getBuildMock.mock.callCount(), 1);
152
+ assert.strictEqual(createServiceMock.mock.callCount(), 0);
153
+ assert.strictEqual(updateServiceMock.mock.callCount(), 1); // 1 dry run
149
154
  const { calls: logCalls } = logAndProgressMock.mock;
150
- assert.match(logCalls[0].arguments[0], /Initiating Cloud Build/);
151
- assert.match(logCalls[1].arguments[0], /Cloud Build job started/);
152
- assert.match(logCalls[2].arguments[0], /failed with status: FAILURE/);
153
- assert.match(logCalls[3].arguments[0], /Build logs:/);
154
- assert.match(logCalls[4].arguments[0], /Attempting to fetch last/);
155
- assert.match(logCalls[5].arguments[0], /Successfully fetched snippet/);
155
+ assert.match(logCalls[0].arguments[0], /Performing dry-run update/);
156
156
  });
157
157
 
158
158
  it('should use buildpacks when no Dockerfile is present', async () => {
159
- const mockBuildId = 'mock-build-id-buildpacks';
160
- const mockSuccessResult = {
161
- id: mockBuildId,
162
- status: 'SUCCESS',
163
- results: { images: [{ name: 'gcr.io/mock-project/mock-image' }] },
164
- };
165
-
166
- const getBuildMock = mock.fn(() => Promise.resolve([mockSuccessResult]));
167
- const createBuildMock = mock.fn(() =>
168
- Promise.resolve([
169
- {
170
- metadata: {
171
- build: {
172
- id: mockBuildId,
173
- },
174
- },
175
- },
176
- ])
177
- );
178
-
179
- const { triggerCloudBuild } = await esmock(
180
- '../../../lib/cloud-api/build.js',
181
- {
182
- '../../../lib/cloud-api/helpers.js': {
183
- callWithRetry: (fn) => fn(),
184
- },
185
- '../../../lib/util/helpers.js': {
186
- logAndProgress: () => {},
187
- },
188
- }
159
+ const { triggerCloudBuild } = await getTriggerCloudBuild();
160
+ const result = await triggerCloudBuild(
161
+ 'mock-project',
162
+ 'mock-location',
163
+ 'mock-bucket',
164
+ 'mock-blob',
165
+ 'mock-repo',
166
+ 'gcr.io/mock-project/mock-image',
167
+ false,
168
+ () => {}
189
169
  );
190
170
 
191
- const context = {
192
- cloudBuildClient: {
193
- createBuild: createBuildMock,
194
- getBuild: getBuildMock,
195
- },
196
- };
171
+ assert.deepStrictEqual(result, mockSuccessResult);
172
+ assert.strictEqual(submitBuildMock.mock.callCount(), 1);
173
+ const submitBuildRequest = submitBuildMock.mock.calls[0].arguments[0];
174
+ assert.deepStrictEqual(submitBuildRequest.buildpackBuild, {});
175
+ assert.strictEqual(submitBuildRequest.dockerBuild, undefined);
176
+ });
197
177
 
178
+ it('should use docker build when Dockerfile is present', async () => {
179
+ const { triggerCloudBuild } = await getTriggerCloudBuild();
198
180
  await triggerCloudBuild(
199
- context,
200
181
  'mock-project',
201
182
  'mock-location',
202
183
  'mock-bucket',
203
184
  'mock-blob',
204
185
  'mock-repo',
205
186
  'gcr.io/mock-project/mock-image',
206
- false, // hasDockerfile = false
187
+ true, // hasDockerfile = true
207
188
  () => {}
208
189
  );
209
190
 
210
- assert.strictEqual(createBuildMock.mock.callCount(), 1);
211
- const buildArg = createBuildMock.mock.calls[0].arguments[0].build;
212
- const buildStep = buildArg.steps[0];
213
- assert.strictEqual(buildStep.name, 'gcr.io/k8s-skaffold/pack');
191
+ assert.strictEqual(submitBuildMock.mock.callCount(), 1);
192
+ const submitBuildRequest = submitBuildMock.mock.calls[0].arguments[0];
193
+ assert.deepStrictEqual(submitBuildRequest.dockerBuild, {});
194
+ assert.strictEqual(submitBuildRequest.buildpackBuild, undefined);
214
195
  });
215
196
 
216
197
  it('should poll for build status until completion', async () => {
217
- const mockBuildId = 'mock-build-id-polling';
218
198
  const mockWorkingResult = { id: mockBuildId, status: 'WORKING' };
219
- const mockSuccessResult = {
220
- id: mockBuildId,
221
- status: 'SUCCESS',
222
- results: { images: [{ name: 'gcr.io/mock-project/mock-image' }] },
223
- };
224
-
225
199
  let getBuildCallCount = 0;
226
- const getBuildMock = mock.fn(() => {
200
+ getBuildMock = mock.fn(() => {
227
201
  getBuildCallCount++;
228
202
  if (getBuildCallCount === 1) {
229
203
  return Promise.resolve([mockWorkingResult]);
230
204
  }
231
205
  return Promise.resolve([mockSuccessResult]);
232
206
  });
207
+ context.cloudBuildClient.getBuild = getBuildMock;
233
208
 
234
- const logAndProgressMock = mock.fn();
235
- const setTimeoutMock = mock.fn((resolve) => resolve());
236
- mock.method(global, 'setTimeout', setTimeoutMock);
237
-
238
- const { triggerCloudBuild } = await esmock(
239
- '../../../lib/cloud-api/build.js',
240
- {
241
- '../../../lib/cloud-api/helpers.js': {
242
- callWithRetry: (fn) => fn(),
243
- },
244
- '../../../lib/util/helpers.js': {
245
- logAndProgress: logAndProgressMock,
246
- },
247
- }
248
- );
249
-
250
- const context = {
251
- cloudBuildClient: {
252
- createBuild: mock.fn(() =>
253
- Promise.resolve([
254
- {
255
- metadata: {
256
- build: {
257
- id: mockBuildId,
258
- },
259
- },
260
- },
261
- ])
262
- ),
263
- getBuild: getBuildMock,
264
- },
265
- };
266
-
209
+ const { triggerCloudBuild } = await getTriggerCloudBuild();
267
210
  await triggerCloudBuild(
268
- context,
269
211
  'mock-project',
270
212
  'mock-location',
271
213
  'mock-bucket',
@@ -280,60 +222,145 @@ describe('triggerCloudBuild', () => {
280
222
  assert.strictEqual(setTimeoutMock.mock.callCount(), 1);
281
223
  assert.strictEqual(setTimeoutMock.mock.calls[0].arguments[1], 5000);
282
224
  const { calls: logCalls } = logAndProgressMock.mock;
283
- assert.match(logCalls[0].arguments[0], /Initiating Cloud Build/);
284
- assert.match(logCalls[1].arguments[0], /Cloud Build job started/);
285
- assert.match(logCalls[2].arguments[0], /Build status: WORKING/);
286
- assert.match(logCalls[3].arguments[0], /completed successfully/);
287
- assert.match(logCalls[4].arguments[0], /Image built/);
225
+ assert.match(logCalls[4].arguments[0], /Build status: WORKING/);
226
+ assert.match(logCalls[5].arguments[0], /completed successfully/);
288
227
  });
289
228
 
290
- it('should handle failed build when no logs are found', async () => {
291
- const mockBuildId = 'mock-build-id-no-logs';
292
- const mockFailureResult = {
293
- id: mockBuildId,
294
- status: 'FAILURE',
295
- logUrl: 'http://mock-log-url.com',
296
- };
229
+ it('should throw an error for a failed build and fetch logs', async () => {
230
+ getBuildMock = mock.fn(() => Promise.resolve([mockFailureResult]));
231
+ context.cloudBuildClient.getBuild = getBuildMock;
297
232
 
298
- const getBuildMock = mock.fn(() => Promise.resolve([mockFailureResult]));
299
- const logAndProgressMock = mock.fn();
300
-
301
- const { triggerCloudBuild } = await esmock(
302
- '../../../lib/cloud-api/build.js',
303
- {
304
- '../../../lib/cloud-api/helpers.js': {
305
- callWithRetry: (fn) => fn(),
306
- },
307
- '../../../lib/util/helpers.js': {
308
- logAndProgress: logAndProgressMock,
309
- },
233
+ const { triggerCloudBuild } = await getTriggerCloudBuild();
234
+ await assert.rejects(
235
+ () =>
236
+ triggerCloudBuild(
237
+ 'mock-project',
238
+ 'mock-location',
239
+ 'mock-bucket',
240
+ 'mock-blob',
241
+ 'mock-repo',
242
+ 'gcr.io/mock-project/mock-image',
243
+ true,
244
+ () => {}
245
+ ),
246
+ (err) => {
247
+ assert.match(err.message, /Build mock-build-id failed/);
248
+ assert.match(err.message, /log line 1/);
249
+ assert.match(err.message, /log line 2/);
250
+ return true;
310
251
  }
311
252
  );
312
253
 
313
- const context = {
314
- cloudBuildClient: {
315
- createBuild: mock.fn(() =>
316
- Promise.resolve([
317
- {
318
- metadata: {
319
- build: {
320
- id: mockBuildId,
321
- },
322
- },
323
- },
324
- ])
254
+ assert.strictEqual(getBuildMock.mock.callCount(), 1);
255
+ assert.strictEqual(getEntriesMock.mock.callCount(), 1);
256
+ assert.strictEqual(setTimeoutMock.mock.callCount(), 1);
257
+ assert.strictEqual(setTimeoutMock.mock.calls[0].arguments[1], 10000);
258
+
259
+ const { calls: logCalls } = logAndProgressMock.mock;
260
+ assert.match(logCalls[4].arguments[0], /failed with status: FAILURE/);
261
+ assert.match(logCalls[5].arguments[0], /Build logs:/);
262
+ assert.match(logCalls[6].arguments[0], /Attempting to fetch last/);
263
+ assert.match(logCalls[7].arguments[0], /Successfully fetched snippet/);
264
+ });
265
+
266
+ it('should throw if dry-run creation fails', async () => {
267
+ createServiceMock = mock.fn(() =>
268
+ Promise.reject(new Error('Dry run fail'))
269
+ );
270
+ context.runClient.createService = createServiceMock;
271
+ const { triggerCloudBuild } = await getTriggerCloudBuild();
272
+ await assert.rejects(
273
+ () =>
274
+ triggerCloudBuild(
275
+ 'mock-project',
276
+ 'mock-location',
277
+ 'mock-bucket',
278
+ 'mock-blob',
279
+ 'mock-repo',
280
+ 'gcr.io/mock-project/mock-image',
281
+ true,
282
+ () => {}
325
283
  ),
326
- getBuild: getBuildMock,
327
- },
328
- loggingClient: {
329
- getEntries: mock.fn(() => Promise.resolve([[]])), // No log entries
330
- },
331
- };
284
+ /Dry-run deployment failed: Dry run fail/
285
+ );
286
+ assert.strictEqual(submitBuildMock.mock.callCount(), 0);
287
+ });
288
+
289
+ it('should throw if dry-run update fails', async () => {
290
+ checkServiceMock.mock.mockImplementation(() => Promise.resolve(true));
291
+ updateServiceMock = mock.fn(() =>
292
+ Promise.reject(new Error('Dry run fail'))
293
+ );
294
+ context.runClient.updateService = updateServiceMock;
295
+ const { triggerCloudBuild } = await getTriggerCloudBuild();
296
+ await assert.rejects(
297
+ () =>
298
+ triggerCloudBuild(
299
+ 'mock-project',
300
+ 'mock-location',
301
+ 'mock-bucket',
302
+ 'mock-blob',
303
+ 'mock-repo',
304
+ 'gcr.io/mock-project/mock-image',
305
+ true,
306
+ () => {}
307
+ ),
308
+ /Dry-run deployment failed: Dry run fail/
309
+ );
310
+ assert.strictEqual(submitBuildMock.mock.callCount(), 0);
311
+ });
312
+
313
+ it('should throw if checkCloudRunServiceExists fails with unexpected error', async () => {
314
+ checkServiceMock.mock.mockImplementation(() =>
315
+ Promise.reject(new Error('Permission denied'))
316
+ );
317
+ const { triggerCloudBuild } = await getTriggerCloudBuild();
318
+ await assert.rejects(
319
+ () =>
320
+ triggerCloudBuild(
321
+ 'mock-project',
322
+ 'mock-location',
323
+ 'mock-bucket',
324
+ 'mock-blob',
325
+ 'mock-repo',
326
+ 'gcr.io/mock-project/mock-image',
327
+ true,
328
+ () => {}
329
+ ),
330
+ /Permission denied/
331
+ );
332
+ });
332
333
 
334
+ it('should throw if submitBuild fails', async () => {
335
+ submitBuildMock = mock.fn(() => Promise.reject(new Error('Submit failed')));
336
+ context.buildsClient.submitBuild = submitBuildMock;
337
+ const { triggerCloudBuild } = await getTriggerCloudBuild();
338
+ await assert.rejects(
339
+ () =>
340
+ triggerCloudBuild(
341
+ 'mock-project',
342
+ 'mock-location',
343
+ 'mock-bucket',
344
+ 'mock-blob',
345
+ 'mock-repo',
346
+ 'gcr.io/mock-project/mock-image',
347
+ true,
348
+ () => {}
349
+ ),
350
+ /Submit failed/
351
+ );
352
+ });
353
+
354
+ it('should handle failed build when no logs are found', async () => {
355
+ getBuildMock = mock.fn(() => Promise.resolve([mockFailureResult]));
356
+ getEntriesMock = mock.fn(() => Promise.resolve([[]])); // No log entries
357
+ context.cloudBuildClient.getBuild = getBuildMock;
358
+ context.loggingClient.getEntries = getEntriesMock;
359
+
360
+ const { triggerCloudBuild } = await getTriggerCloudBuild();
333
361
  await assert.rejects(
334
362
  () =>
335
363
  triggerCloudBuild(
336
- context,
337
364
  'mock-project',
338
365
  'mock-location',
339
366
  'mock-bucket',
@@ -344,68 +371,29 @@ describe('triggerCloudBuild', () => {
344
371
  () => {}
345
372
  ),
346
373
  (err) => {
347
- assert.match(err.message, /Build mock-build-id-no-logs failed/);
348
- assert.doesNotMatch(err.message, /Last log lines/);
374
+ assert.match(err.message, /Build mock-build-id failed/);
375
+ assert.doesNotMatch(err.message, /Last log lines from build/);
349
376
  return true;
350
377
  }
351
378
  );
352
379
 
353
380
  const { calls: logCalls } = logAndProgressMock.mock;
354
- assert.match(logCalls[0].arguments[0], /Initiating Cloud Build/);
355
- assert.match(logCalls[1].arguments[0], /Cloud Build job started/);
356
- assert.match(logCalls[2].arguments[0], /failed with status: FAILURE/);
357
- assert.match(logCalls[3].arguments[0], /Build logs:/);
358
- assert.match(logCalls[4].arguments[0], /Attempting to fetch last/);
359
- assert.match(logCalls[5].arguments[0], /No specific log entries retrieved/);
381
+ assert.match(logCalls[6].arguments[0], /Attempting to fetch last/);
382
+ assert.match(logCalls[7].arguments[0], /No specific log entries retrieved/);
360
383
  });
361
384
 
362
385
  it('should handle error when fetching logs for a failed build', async () => {
363
- const mockBuildId = 'mock-build-id-log-error';
364
- const mockFailureResult = {
365
- id: mockBuildId,
366
- status: 'FAILURE',
367
- logUrl: 'http://mock-log-url.com',
368
- };
369
-
370
- const getBuildMock = mock.fn(() => Promise.resolve([mockFailureResult]));
371
- const logAndProgressMock = mock.fn();
372
-
373
- const { triggerCloudBuild } = await esmock(
374
- '../../../lib/cloud-api/build.js',
375
- {
376
- '../../../lib/cloud-api/helpers.js': {
377
- callWithRetry: (fn) => fn(),
378
- },
379
- '../../../lib/util/helpers.js': {
380
- logAndProgress: logAndProgressMock,
381
- },
382
- }
386
+ getBuildMock = mock.fn(() => Promise.resolve([mockFailureResult]));
387
+ getEntriesMock = mock.fn(() =>
388
+ Promise.reject(new Error('Log fetch error'))
383
389
  );
390
+ context.cloudBuildClient.getBuild = getBuildMock;
391
+ context.loggingClient.getEntries = getEntriesMock;
384
392
 
385
- const context = {
386
- cloudBuildClient: {
387
- createBuild: mock.fn(() =>
388
- Promise.resolve([
389
- {
390
- metadata: {
391
- build: {
392
- id: mockBuildId,
393
- },
394
- },
395
- },
396
- ])
397
- ),
398
- getBuild: getBuildMock,
399
- },
400
- loggingClient: {
401
- getEntries: mock.fn(() => Promise.reject(new Error('Log fetch error'))),
402
- },
403
- };
404
-
393
+ const { triggerCloudBuild } = await getTriggerCloudBuild();
405
394
  await assert.rejects(
406
395
  () =>
407
396
  triggerCloudBuild(
408
- context,
409
397
  'mock-project',
410
398
  'mock-location',
411
399
  'mock-bucket',
@@ -416,20 +404,16 @@ describe('triggerCloudBuild', () => {
416
404
  () => {}
417
405
  ),
418
406
  (err) => {
419
- assert.match(err.message, /Build mock-build-id-log-error failed/);
420
- assert.doesNotMatch(err.message, /Last log lines/);
407
+ assert.match(err.message, /Build mock-build-id failed/);
408
+ assert.doesNotMatch(err.message, /Last log lines from build/);
421
409
  return true;
422
410
  }
423
411
  );
424
412
 
425
413
  const { calls: logCalls } = logAndProgressMock.mock;
426
- assert.match(logCalls[0].arguments[0], /Initiating Cloud Build/);
427
- assert.match(logCalls[1].arguments[0], /Cloud Build job started/);
428
- assert.match(logCalls[2].arguments[0], /failed with status: FAILURE/);
429
- assert.match(logCalls[3].arguments[0], /Build logs:/);
430
- assert.match(logCalls[4].arguments[0], /Attempting to fetch last/);
414
+ assert.match(logCalls[6].arguments[0], /Attempting to fetch last/);
431
415
  assert.match(
432
- logCalls[5].arguments[0],
416
+ logCalls[7].arguments[0],
433
417
  /Failed to fetch build logs snippet/
434
418
  );
435
419
  });