Skip to content

Commit d2bf72e

Browse files
committed
more tests
1 parent 7d5ab56 commit d2bf72e

File tree

5 files changed

+369
-0
lines changed

5 files changed

+369
-0
lines changed

packages/@aws-cdk/tmp-toolkit-helpers/src/util/type-brands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export type Branded<T, B> = T & Brand<B>;
3737
* values which are branded by construction (really just an elaborate
3838
* way to write 'as').
3939
*/
40+
/* c8 ignore next 3 */
4041
export function createBranded<A extends Branded<any, any>>(value: TypeUnderlyingBrand<A>): A {
4142
return value as A;
4243
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import { exec as _exec } from 'child_process';
2+
import * as fs from 'fs';
3+
import * as os from 'os';
4+
import * as path from 'path';
5+
import { promisify } from 'util';
6+
import { zipDirectory } from '../../src/util/archive';
7+
8+
const exec = promisify(_exec);
9+
10+
describe('zipDirectory', () => {
11+
let tempDir: string;
12+
let outputZipPath: string;
13+
14+
beforeEach(async () => {
15+
// Create a temporary directory
16+
tempDir = await fs.promises.mkdtemp(
17+
path.join(os.tmpdir(), 'zipDirectory-test-'),
18+
);
19+
outputZipPath = path.join(os.tmpdir(), 'output.zip');
20+
21+
// Clean up any existing test files
22+
try {
23+
await fs.promises.unlink(outputZipPath);
24+
} catch (e) {
25+
// Ignore errors if file doesn't exist
26+
}
27+
});
28+
29+
afterEach(async () => {
30+
// Clean up
31+
try {
32+
await fs.promises.rm(tempDir, { recursive: true });
33+
await fs.promises.unlink(outputZipPath);
34+
} catch (e) {
35+
// Ignore errors during cleanup
36+
}
37+
});
38+
39+
test('creates a zip file with expected files', async () => {
40+
// Setup
41+
const testFileContent = 'test content';
42+
const testFilePath = path.join(tempDir, 'testfile.txt');
43+
await fs.promises.writeFile(testFilePath, testFileContent);
44+
45+
// Create a nested directory
46+
const nestedDir = path.join(tempDir, 'nested');
47+
await fs.promises.mkdir(nestedDir, { recursive: true });
48+
const nestedFilePath = path.join(nestedDir, 'nestedfile.txt');
49+
await fs.promises.writeFile(nestedFilePath, 'nested content');
50+
51+
// Act
52+
await zipDirectory(tempDir, outputZipPath);
53+
54+
// Assert
55+
const stats = await fs.promises.stat(outputZipPath);
56+
expect(stats.isFile()).toBe(true);
57+
58+
// Verify content using unzip
59+
const { stdout: fileList } = await exec(`unzip -l ${outputZipPath}`);
60+
61+
// Check file list contains the expected files
62+
expect(fileList).toContain('testfile.txt');
63+
expect(fileList).toContain('nested/nestedfile.txt');
64+
65+
// Create a temporary directory to extract files
66+
const extractDir = await fs.promises.mkdtemp(
67+
path.join(os.tmpdir(), 'extract-test-'),
68+
);
69+
70+
try {
71+
// Extract files
72+
await exec(`unzip ${outputZipPath} -d ${extractDir}`);
73+
74+
// Verify content of files
75+
const mainFileContent = await fs.promises.readFile(
76+
path.join(extractDir, 'testfile.txt'),
77+
{ encoding: 'utf-8' },
78+
);
79+
expect(mainFileContent).toBe(testFileContent);
80+
81+
const nestedFileContent = await fs.promises.readFile(
82+
path.join(extractDir, 'nested/nestedfile.txt'),
83+
{ encoding: 'utf-8' },
84+
);
85+
expect(nestedFileContent).toBe('nested content');
86+
} finally {
87+
// Clean up the extract directory
88+
await fs.promises.rm(extractDir, { recursive: true });
89+
}
90+
});
91+
92+
test('preserves file permissions', async () => {
93+
// Setup - create a file with specific permissions
94+
const testFilePath = path.join(tempDir, 'executable.sh');
95+
await fs.promises.writeFile(testFilePath, '#!/bin/sh\necho "Hello"');
96+
97+
// Set executable permissions (0755)
98+
await fs.promises.chmod(testFilePath, 0o755);
99+
100+
// Act
101+
await zipDirectory(tempDir, outputZipPath);
102+
103+
// Assert - extract the file and check permissions
104+
// Create a temporary directory to extract files
105+
const extractDir = await fs.promises.mkdtemp(
106+
path.join(os.tmpdir(), 'extract-permissions-test-'),
107+
);
108+
109+
try {
110+
// Extract files
111+
await exec(`unzip ${outputZipPath} -d ${extractDir}`);
112+
113+
// Check the extracted file permissions
114+
const stats = await fs.promises.stat(path.join(extractDir, 'executable.sh'));
115+
116+
// Check executable bit is set (0o111 = ---x--x--x)
117+
// eslint-disable-next-line no-bitwise
118+
const isExecutable = !!(stats.mode & 0o111); // Check if any executable bit is set
119+
expect(isExecutable).toBeTruthy();
120+
} finally {
121+
// Clean up the extract directory
122+
await fs.promises.rm(extractDir, { recursive: true });
123+
}
124+
});
125+
126+
test('follows symlinks as expected', async () => {
127+
// Skip test on Windows as symlinks might not be properly supported
128+
if (os.platform() === 'win32') {
129+
return;
130+
}
131+
132+
// Setup - create a file and a symlink to it
133+
const targetFile = path.join(tempDir, 'target.txt');
134+
await fs.promises.writeFile(targetFile, 'target content');
135+
136+
const symlinkPath = path.join(tempDir, 'link.txt');
137+
await fs.promises.symlink(targetFile, symlinkPath);
138+
139+
// Act
140+
await zipDirectory(tempDir, outputZipPath);
141+
142+
// Assert - extract the file and check content
143+
// Create a temporary directory to extract files
144+
const extractDir = await fs.promises.mkdtemp(
145+
path.join(os.tmpdir(), 'extract-symlink-test-'),
146+
);
147+
148+
try {
149+
// Extract files
150+
await exec(`unzip ${outputZipPath} -d ${extractDir}`);
151+
152+
// Check that the link.txt file exists in the zip
153+
const linkExists = await fs.promises.stat(path.join(extractDir, 'link.txt'))
154+
.then(() => true)
155+
.catch(() => false);
156+
expect(linkExists).toBeTruthy();
157+
158+
// Check that the content is the same as the target file
159+
const linkedContent = await fs.promises.readFile(
160+
path.join(extractDir, 'link.txt'),
161+
{ encoding: 'utf-8' },
162+
);
163+
expect(linkedContent).toBe('target content');
164+
} finally {
165+
// Clean up the extract directory
166+
await fs.promises.rm(extractDir, { recursive: true });
167+
}
168+
});
169+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { numberFromBool } from '../../src/util/bool';
2+
3+
test.each([
4+
[true, 1],
5+
[false, 0],
6+
])('for %s returns %s', (value, expected) => {
7+
expect(numberFromBool(value)).toBe(expected);
8+
});
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import * as crypto from 'crypto';
2+
import { contentHash, contentHashAny } from '../../src/util/content-hash';
3+
4+
describe('contentHash', () => {
5+
test('hashes string data correctly', () => {
6+
const data = 'test string';
7+
const expected = crypto.createHash('sha256').update(data).digest('hex');
8+
expect(contentHash(data)).toEqual(expected);
9+
});
10+
11+
test('hashes Buffer data correctly', () => {
12+
const data = Buffer.from('test buffer');
13+
const expected = crypto.createHash('sha256').update(data).digest('hex');
14+
expect(contentHash(data)).toEqual(expected);
15+
});
16+
17+
test('hashes DataView data correctly', () => {
18+
const buffer = new ArrayBuffer(4);
19+
const view = new DataView(buffer);
20+
view.setUint32(0, 42);
21+
const expected = crypto.createHash('sha256').update(view).digest('hex');
22+
expect(contentHash(view)).toEqual(expected);
23+
});
24+
25+
test('produces consistent output for identical inputs', () => {
26+
const data = 'consistent data';
27+
expect(contentHash(data)).toEqual(contentHash(data));
28+
});
29+
30+
test('produces different output for different inputs', () => {
31+
expect(contentHash('data1')).not.toEqual(contentHash('data2'));
32+
});
33+
});
34+
35+
describe('contentHashAny', () => {
36+
test('hashes primitive string correctly', () => {
37+
const value = 'test string';
38+
const result = contentHashAny(value);
39+
expect(result).toMatch(/^[a-f0-9]{64}$/); // sha256 produces 64 hex chars
40+
});
41+
42+
test('hashes primitive number correctly', () => {
43+
const value = 123;
44+
const result = contentHashAny(value);
45+
expect(result).toMatch(/^[a-f0-9]{64}$/);
46+
});
47+
48+
test('hashes primitive boolean correctly', () => {
49+
const value = true;
50+
const result = contentHashAny(value);
51+
expect(result).toMatch(/^[a-f0-9]{64}$/);
52+
});
53+
54+
test('hashes null and undefined correctly', () => {
55+
expect(contentHashAny(null)).toMatch(/^[a-f0-9]{64}$/);
56+
expect(contentHashAny(undefined)).toMatch(/^[a-f0-9]{64}$/);
57+
});
58+
59+
test('hashes arrays correctly', () => {
60+
const value = ['a', 'b', 'c'];
61+
const result = contentHashAny(value);
62+
expect(result).toMatch(/^[a-f0-9]{64}$/);
63+
});
64+
65+
test('hashes nested arrays correctly', () => {
66+
const value = ['a', ['b', 'c'], 'd'];
67+
const result = contentHashAny(value);
68+
expect(result).toMatch(/^[a-f0-9]{64}$/);
69+
});
70+
71+
test('hashes objects correctly', () => {
72+
const value = { a: 1, b: 2, c: 3 };
73+
const result = contentHashAny(value);
74+
expect(result).toMatch(/^[a-f0-9]{64}$/);
75+
});
76+
77+
test('hashes nested objects correctly', () => {
78+
const value = { a: 1, b: { c: 2, d: 3 }, e: 4 };
79+
const result = contentHashAny(value);
80+
expect(result).toMatch(/^[a-f0-9]{64}$/);
81+
});
82+
83+
test('hashes complex mixed structures correctly', () => {
84+
const value = {
85+
a: 1,
86+
b: ['x', 'y', 'z'],
87+
c: { d: true, e: [1, 2, { f: 'g' }] },
88+
h: null,
89+
};
90+
const result = contentHashAny(value);
91+
expect(result).toMatch(/^[a-f0-9]{64}$/);
92+
});
93+
94+
test('produces consistent output for identical inputs', () => {
95+
const value = { a: 1, b: [2, 3], c: { d: 4 } };
96+
expect(contentHashAny(value)).toEqual(contentHashAny(value));
97+
});
98+
99+
test('produces different output for different inputs', () => {
100+
const value1 = { a: 1, b: 2 };
101+
const value2 = { a: 1, b: 3 };
102+
expect(contentHashAny(value1)).not.toEqual(contentHashAny(value2));
103+
});
104+
105+
test('produces same hash regardless of object property order', () => {
106+
const value1 = { a: 1, b: 2, c: 3 };
107+
const value2 = { c: 3, a: 1, b: 2 };
108+
expect(contentHashAny(value1)).toEqual(contentHashAny(value2));
109+
});
110+
111+
test('distinguishes between string and number values', () => {
112+
expect(contentHashAny('123')).not.toEqual(contentHashAny(123));
113+
});
114+
115+
test('distinguishes between empty arrays and objects', () => {
116+
expect(contentHashAny([])).not.toEqual(contentHashAny({}));
117+
});
118+
});
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { padLeft, padRight, formatTime } from '../../src/util/string-manipulation';
2+
3+
describe('string-manipulation', () => {
4+
describe('padLeft', () => {
5+
test('adds padding to the left of a string', () => {
6+
expect(padLeft(5, 'abc')).toBe(' abc');
7+
});
8+
9+
test('returns the string unchanged if it is already at the target length', () => {
10+
expect(padLeft(3, 'abc')).toBe('abc');
11+
});
12+
13+
test('returns the string unchanged if it exceeds the target length', () => {
14+
expect(padLeft(2, 'abc')).toBe('abc');
15+
});
16+
17+
test('uses the specified padding character', () => {
18+
expect(padLeft(5, 'abc', '*')).toBe('**abc');
19+
});
20+
21+
test('handles empty strings', () => {
22+
expect(padLeft(3, '')).toBe(' ');
23+
});
24+
});
25+
26+
describe('padRight', () => {
27+
test('adds padding to the right of a string', () => {
28+
expect(padRight(5, 'abc')).toBe('abc ');
29+
});
30+
31+
test('returns the string unchanged if it is already at the target length', () => {
32+
expect(padRight(3, 'abc')).toBe('abc');
33+
});
34+
35+
test('returns the string unchanged if it exceeds the target length', () => {
36+
expect(padRight(2, 'abc')).toBe('abc');
37+
});
38+
39+
test('uses the specified padding character', () => {
40+
expect(padRight(5, 'abc', '*')).toBe('abc**');
41+
});
42+
43+
test('handles empty strings', () => {
44+
expect(padRight(3, '')).toBe(' ');
45+
});
46+
});
47+
48+
describe('formatTime', () => {
49+
test('converts milliseconds to seconds and rounds to 2 decimal places', () => {
50+
expect(formatTime(1234)).toBe(1.23);
51+
});
52+
53+
test('rounds up correctly', () => {
54+
expect(formatTime(1235)).toBe(1.24);
55+
});
56+
57+
test('rounds down correctly', () => {
58+
expect(formatTime(1234.4)).toBe(1.23);
59+
});
60+
61+
test('handles zero', () => {
62+
expect(formatTime(0)).toBe(0);
63+
});
64+
65+
test('handles large numbers', () => {
66+
expect(formatTime(60000)).toBe(60);
67+
});
68+
69+
test('handles decimal precision correctly', () => {
70+
expect(formatTime(1500)).toBe(1.5);
71+
});
72+
});
73+
});

0 commit comments

Comments
 (0)