@ibm-cloud/cd-tools 1.2.2 → 1.2.4

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/test/setup.js CHANGED
@@ -23,10 +23,23 @@ const TEMP_DIR = resolve(nconf.get('TEST_TEMP_DIR'));
23
23
  const LOG_DIR = resolve(nconf.get('TEST_LOG_DIR'));
24
24
  const DEBUG_MODE = nconf.get('TEST_DEBUG_MODE');
25
25
 
26
+ function sleep(ms) {
27
+ return new Promise((resolve) => {
28
+ setTimeout(resolve, ms);
29
+ });
30
+ }
31
+
26
32
  export const mochaHooks = {
27
- beforeAll() {
28
- if (!fs.existsSync(TEMP_DIR)) fs.mkdirSync(TEMP_DIR, { recursive: true });
29
- if (fs.existsSync(LOG_DIR)) fs.rmSync(LOG_DIR, { recursive: true });
33
+ async beforeAll() {
34
+ for (let i = 0; i < 3; i++) {
35
+ try {
36
+ fs.rmSync(TEMP_DIR, { recursive: true, force: true });
37
+ fs.rmSync(LOG_DIR, { recursive: true, force: true });
38
+ } catch {}
39
+ if (!fs.existsSync(TEMP_DIR) && !fs.existsSync(LOG_DIR)) break;
40
+ await sleep(1000);
41
+ }
42
+ fs.mkdirSync(TEMP_DIR, { recursive: true });
30
43
  },
31
44
  beforeEach() {
32
45
  if (DEBUG_MODE === true && LOG_DIR) {
@@ -35,10 +48,14 @@ export const mochaHooks = {
35
48
  resolve(LOG_DIR, this.currentTest.parent.command, testTitle + '.log') :
36
49
  resolve(LOG_DIR, testTitle + '.log');
37
50
  logger.createLogStream(logFile);
51
+ // Adding logging for log stream creation and closing, because there's cases of missing test log files when running tests in parallel, most likely because of the singleton logger,
52
+ // causing some sort of race condition happening
53
+ // console.info(`Created test log stream for test case '${this.currentTest.title}'`);
38
54
  }
39
55
  },
40
- afterEach() {
41
- logger.close();
56
+ async afterEach() {
57
+ await logger.close();
58
+ // console.info(`Closed test log stream for test case '${this.currentTest.title}'`);
42
59
  },
43
60
  afterAll() {
44
61
  if (fs.existsSync(TEMP_DIR) && DEBUG_MODE === false) fs.rmSync(TEMP_DIR, { recursive: true });
@@ -8,11 +8,14 @@
8
8
  */
9
9
 
10
10
  import { promisify } from 'util';
11
+ import fs from 'fs';
12
+ import path from 'path';
11
13
  import child_process from 'child_process';
12
14
  import stripAnsi from 'strip-ansi';
13
15
  import pty from 'node-pty';
16
+ import { parse as tfToJson } from '@cdktf/hcl2json'
14
17
  import nconf from 'nconf';
15
- import { expect } from 'chai';
18
+ import { expect, assert } from 'chai';
16
19
 
17
20
  import { getBearerToken, deleteToolchain } from '../../cmd/utils/requests.js';
18
21
  import { logger } from '../../cmd/utils/logger.js';
@@ -20,15 +23,53 @@ import { logger } from '../../cmd/utils/logger.js';
20
23
  nconf.env('__');
21
24
  nconf.file('local', 'test/config/local.json');
22
25
 
26
+ const TEMP_DIR = nconf.get('TEST_TEMP_DIR');
23
27
  const IBMCLOUD_API_KEY = nconf.get('IBMCLOUD_API_KEY');
24
28
 
25
29
  function cleanOutput(data) {
26
30
  if (typeof data === 'string') return stripAnsi(data).replace(/\r/g, '').trim();
27
31
  }
28
32
 
33
+ function searchDirectory(currentPath) {
34
+ const foundFiles = [];
35
+ const entries = fs.readdirSync(currentPath, { withFileTypes: true });
36
+ for (const entry of entries) {
37
+ const fullPath = path.join(currentPath, entry.name);
38
+ if (entry.isDirectory()) {
39
+ foundFiles.push(...searchDirectory(fullPath));
40
+ } else {
41
+ foundFiles.push(path.join(currentPath, entry.name));
42
+ }
43
+ }
44
+ return foundFiles;
45
+ }
46
+
47
+ export function parseTcIdAndRegion(output) {
48
+ const pattern = /See cloned toolchain: https:\/\/cloud\.ibm\.com\/devops\/toolchains\/([a-zA-Z0-9-]+)\?env_id=ibm\:yp\:([a-zA-Z0-9-]+)/;
49
+ const match = output.match(pattern);
50
+
51
+ if (match) {
52
+ const toolchainId = match[1];
53
+ const region = match[2];
54
+ return { toolchainId, region };
55
+ } else {
56
+ return null;
57
+ }
58
+ }
59
+
29
60
  export async function execCommand(fullCommand, options) {
30
61
  const commandStr = `node ${fullCommand.join(' ')}`;
31
62
  const execPromise = promisify(child_process.exec);
63
+
64
+ if (!options) {
65
+ options = { cwd: TEMP_DIR }
66
+ } else {
67
+ options.cwd ??= TEMP_DIR;
68
+ if (!fs.existsSync(options.cwd)) {
69
+ fs.mkdirSync(options.cwd, { recursive: true });
70
+ }
71
+ }
72
+
32
73
  try {
33
74
  const { stdout, stderr } = await execPromise(commandStr, options);
34
75
  if (stderr) {
@@ -51,12 +92,14 @@ export async function execCommand(fullCommand, options) {
51
92
  export function runPtyProcess(fullCommand, options) {
52
93
  const {
53
94
  timeout = 0,
54
- cwd = process.cwd(),
95
+ cwd = TEMP_DIR,
55
96
  env = process.env,
56
97
  questionAnswerMap = {},
57
98
  exitCondition = '',
58
99
  } = options;
59
100
 
101
+ if (!fs.existsSync(cwd)) fs.mkdirSync(cwd, { recursive: true });
102
+
60
103
  return new Promise((resolve, reject) => {
61
104
  try {
62
105
  const ptyProcess = pty.spawn('node', fullCommand, {
@@ -106,7 +149,7 @@ export function runPtyProcess(fullCommand, options) {
106
149
  });
107
150
  }
108
151
 
109
- export async function testSuiteCleanup(toolchainsToDelete) {
152
+ export async function deleteCreatedToolchains(toolchainsToDelete) {
110
153
  if (toolchainsToDelete && typeof toolchainsToDelete === 'object' && toolchainsToDelete.size > 0) {
111
154
  const token = await getBearerToken(IBMCLOUD_API_KEY);
112
155
  const deletePromises = [...toolchainsToDelete.entries()].map(([id, region]) => deleteToolchain(token, id, region));
@@ -114,24 +157,83 @@ export async function testSuiteCleanup(toolchainsToDelete) {
114
157
  }
115
158
  }
116
159
 
117
- export async function expectExecError(fullCommand, expectedMessage, options) {
160
+ export async function assertExecError(fullCommand, expectedMessage, options, assertionFn) {
118
161
  try {
119
162
  const output = await execCommand(fullCommand, options);
120
163
  logger.dump(output);
121
164
  throw new Error('Expected command to fail but it succeeded');
122
165
  } catch (e) {
123
166
  logger.dump(e.message);
124
- expect(e.message).to.match(expectedMessage);
167
+ if (assertionFn) {
168
+ const res = assertionFn(e.message);
169
+ if (res instanceof Promise) await res;
170
+ } else if (expectedMessage) {
171
+ expect(e.message).to.match(expectedMessage);
172
+ } else {
173
+ assert.fail('No assertion function or expected message provided.');
174
+ }
125
175
  }
126
176
  }
127
177
 
128
- export async function expectPtyOutputToMatch(fullCommand, expectedMessage, options) {
178
+ export async function assertPtyOutput(fullCommand, expectedMessage, options, assertionFn) {
129
179
  try {
130
180
  const output = await runPtyProcess(fullCommand, options);
131
181
  logger.dump(output);
132
- expect(output).to.match(expectedMessage);
182
+ if (assertionFn) {
183
+ const res = assertionFn(output);
184
+ if (res instanceof Promise) await res;
185
+ } else if (expectedMessage) {
186
+ expect(output).to.match(expectedMessage);
187
+ } else {
188
+ assert.fail('No assertion function or expected message provided.');
189
+ }
190
+ return parseTcIdAndRegion(output);
133
191
  } catch (e) {
134
192
  logger.dump(e.message);
135
193
  throw (e);
136
194
  }
137
195
  }
196
+
197
+ export function areFilesInDir(dirPath, filePatterns) {
198
+ const foundFiles = searchDirectory(dirPath);
199
+ for (const pattern of filePatterns) {
200
+ const regex = new RegExp(pattern);
201
+ if (!foundFiles.some(file => regex.test(file))) {
202
+ return false;
203
+ }
204
+ }
205
+ return true;
206
+ }
207
+
208
+ export async function assertTfResourcesInDir(dirPath, expectedResourcesMap) {
209
+ const resourceCounter = {};
210
+
211
+ const foundFiles = searchDirectory(dirPath);
212
+ const allResources = [];
213
+ for (const file of foundFiles) {
214
+ if (!file.endsWith('.tf')) continue;
215
+ const fileName = path.basename(file);
216
+ const tfFile = fs.readFileSync(file, 'utf8');
217
+ const tfFileObject = await tfToJson(fileName, tfFile);
218
+ if (tfFileObject.resource) allResources.push(tfFileObject.resource);
219
+ }
220
+
221
+ for (const resourceMap of allResources) {
222
+ for (const resourceType of Object.keys(resourceMap)) {
223
+ resourceCounter[resourceType] = (resourceCounter[resourceType] || 0) + 1;
224
+ }
225
+ }
226
+ // Check if all expected resources are present
227
+ for (const [resourceType, expectedCount] of Object.entries(expectedResourcesMap)) {
228
+ if (resourceCounter[resourceType] !== expectedCount) {
229
+ assert.fail(`Expected ${expectedCount} ${resourceType} resource(s) but found ${resourceCounter[resourceType] || 0}`);
230
+ }
231
+ }
232
+ // Check if there are unexpected resources
233
+ for (const [resourceType, count] of Object.entries(resourceCounter)) {
234
+ if (!(resourceType in expectedResourcesMap)) {
235
+ assert.fail(`Unexpected ${resourceType} resource found. (Count: ${count})`);
236
+ }
237
+ }
238
+ assert.ok(true, 'Directory contains all expected resources');
239
+ }
@@ -1,11 +0,0 @@
1
- /**
2
- * Licensed Materials - Property of IBM
3
- * (c) Copyright IBM Corporation 2025. All Rights Reserved.
4
- *
5
- * Note to U.S. Government Users Restricted Rights:
6
- * Use, duplication or disclosure restricted by GSA ADP Schedule
7
- * Contract with IBM Corp.
8
- */
9
-
10
- describe('copy-toolchain: Test import-terraform output', function () {
11
- });
@@ -1,11 +0,0 @@
1
- /**
2
- * Licensed Materials - Property of IBM
3
- * (c) Copyright IBM Corporation 2025. All Rights Reserved.
4
- *
5
- * Note to U.S. Government Users Restricted Rights:
6
- * Use, duplication or disclosure restricted by GSA ADP Schedule
7
- * Contract with IBM Corp.
8
- */
9
-
10
- describe('copy-toolchain: Test Terraform output', function () {
11
- });