Skip to content

Commit 01c32b0

Browse files
committed
added tests for coverage
1 parent fb47a5a commit 01c32b0

File tree

7 files changed

+606
-1
lines changed

7 files changed

+606
-1
lines changed

src/atlclients/strategyCrypto.test.ts

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Mock crypto module at the top
2+
class MockHash {
3+
private data: string = '';
4+
5+
update(data: any): this {
6+
this.data += data.toString();
7+
return this;
8+
}
9+
10+
digest(): Buffer {
11+
// Return a predictable hash for testing
12+
const hash = Buffer.alloc(32);
13+
for (let i = 0; i < 32; i++) {
14+
hash[i] = (this.data.charCodeAt(i % this.data.length) + i) % 256;
15+
}
16+
return hash;
17+
}
18+
}
19+
20+
jest.mock('crypto', () => ({
21+
default: {
22+
createHash: () => new MockHash(),
23+
randomBytes: (size: number) => {
24+
// Return predictable bytes for testing
25+
const buffer = Buffer.alloc(size);
26+
for (let i = 0; i < size; i++) {
27+
buffer[i] = i % 256;
28+
}
29+
return buffer;
30+
},
31+
},
32+
}));
33+
34+
import { base64URLEncode, basicAuth, createVerifier, sha256 } from './strategyCrypto';
35+
36+
describe('strategyCrypto', () => {
37+
describe('basicAuth', () => {
38+
it('should create basic auth string with username and password', () => {
39+
const result = basicAuth('testuser', 'testpass');
40+
expect(result).toBe('Basic dGVzdHVzZXI6dGVzdHBhc3M=');
41+
});
42+
43+
it('should handle empty username and password', () => {
44+
const result = basicAuth('', '');
45+
expect(result).toBe('Basic Og==');
46+
});
47+
48+
it('should handle special characters in username and password', () => {
49+
const result = basicAuth('[email protected]', 'p@ssw0rd!');
50+
expect(result).toBe('Basic dXNlckBkb21haW4uY29tOnBAc3N3MHJkIQ==');
51+
});
52+
53+
it('should handle unicode characters', () => {
54+
const result = basicAuth('用户', '密码');
55+
expect(result).toContain('Basic ');
56+
expect(result.length).toBeGreaterThan('Basic '.length);
57+
});
58+
});
59+
60+
describe('base64URLEncode', () => {
61+
it('should encode buffer to base64 URL safe string', () => {
62+
const buffer = Buffer.from('hello world');
63+
const result = base64URLEncode(buffer);
64+
expect(result).toBe('aGVsbG8gd29ybGQ');
65+
});
66+
67+
it('should replace URL unsafe characters', () => {
68+
// Create a buffer that will have + and / characters in base64
69+
const buffer = Buffer.from('hello>world?test');
70+
const result = base64URLEncode(buffer);
71+
expect(result).not.toContain('+');
72+
expect(result).not.toContain('/');
73+
expect(result).not.toContain('=');
74+
});
75+
76+
it('should handle empty buffer', () => {
77+
const buffer = Buffer.from('');
78+
const result = base64URLEncode(buffer);
79+
expect(result).toBe('');
80+
});
81+
82+
it('should handle binary data', () => {
83+
const buffer = Buffer.from([0x00, 0x01, 0x02, 0x03, 0xff]);
84+
const result = base64URLEncode(buffer);
85+
expect(result).toBe('AAECA_8'); // Corrected expected value
86+
});
87+
});
88+
89+
describe('sha256', () => {
90+
it('should create sha256 hash from string', () => {
91+
const result = sha256('hello world');
92+
expect(result).toBeInstanceOf(Buffer);
93+
expect(result.length).toBe(32); // SHA256 produces 32 bytes
94+
});
95+
96+
it('should create sha256 hash from buffer', () => {
97+
const buffer = Buffer.from('test data');
98+
const result = sha256(buffer);
99+
expect(result).toBeInstanceOf(Buffer);
100+
expect(result.length).toBe(32);
101+
});
102+
103+
it('should produce consistent hash for same input', () => {
104+
const input = 'consistent input';
105+
const result1 = sha256(input);
106+
const result2 = sha256(input);
107+
expect(result1.equals(result2)).toBe(true);
108+
});
109+
110+
it('should produce different hashes for different inputs', () => {
111+
const result1 = sha256('input1');
112+
const result2 = sha256('input2');
113+
expect(result1.equals(result2)).toBe(false);
114+
});
115+
116+
it('should handle empty string', () => {
117+
const result = sha256('');
118+
expect(result).toBeInstanceOf(Buffer);
119+
expect(result.length).toBe(32);
120+
});
121+
});
122+
123+
describe('createVerifier', () => {
124+
it('should create a verifier string', () => {
125+
const result = createVerifier();
126+
expect(typeof result).toBe('string');
127+
expect(result.length).toBeGreaterThan(0);
128+
});
129+
130+
it('should create consistent verifiers with mocked crypto', () => {
131+
const result1 = createVerifier();
132+
const result2 = createVerifier();
133+
// With our mock, results should be the same
134+
expect(result1).toBe(result2);
135+
});
136+
137+
it('should create URL-safe verifier string', () => {
138+
const result = createVerifier();
139+
expect(result).not.toContain('+');
140+
expect(result).not.toContain('/');
141+
expect(result).not.toContain('=');
142+
});
143+
144+
it('should create verifier of expected length', () => {
145+
const result = createVerifier();
146+
// 32 bytes base64URL encoded should be 43 characters (without padding)
147+
expect(result.length).toBe(43);
148+
});
149+
});
150+
});

src/bitbucket/bbUtils.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { encodePathParts } from './bbUtils';
2+
3+
describe('bbUtils', () => {
4+
describe('encodePathParts', () => {
5+
it('should encode path parts with special characters', () => {
6+
const path = 'folder/sub folder/file with spaces.txt';
7+
const result = encodePathParts(path);
8+
expect(result).toBe('folder/sub%20folder/file%20with%20spaces.txt');
9+
});
10+
11+
it('should handle paths with special URL characters', () => {
12+
const path = 'folder/sub&folder/file?param=value.txt';
13+
const result = encodePathParts(path);
14+
expect(result).toBe('folder/sub%26folder/file%3Fparam%3Dvalue.txt');
15+
});
16+
17+
it('should handle empty path parts', () => {
18+
const path = 'folder//subfolder';
19+
const result = encodePathParts(path);
20+
expect(result).toBe('folder//subfolder');
21+
});
22+
23+
it('should handle single folder name', () => {
24+
const path = 'folder name';
25+
const result = encodePathParts(path);
26+
expect(result).toBe('folder%20name');
27+
});
28+
29+
it('should handle empty string', () => {
30+
const path = '';
31+
const result = encodePathParts(path);
32+
expect(result).toBe('');
33+
});
34+
35+
it('should handle null/undefined input', () => {
36+
expect(encodePathParts(null as any)).toBe(undefined);
37+
expect(encodePathParts(undefined as any)).toBe(undefined);
38+
});
39+
40+
it('should handle paths with unicode characters', () => {
41+
const path = 'folder/测试文件/файл.txt';
42+
const result = encodePathParts(path);
43+
expect(result).toBe('folder/%E6%B5%8B%E8%AF%95%E6%96%87%E4%BB%B6/%D1%84%D0%B0%D0%B9%D0%BB.txt');
44+
});
45+
});
46+
});
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import * as vscode from 'vscode';
2+
3+
import {
4+
DevsphereConfigurationManager,
5+
executeVSCodeCommand,
6+
getDefaultLifecycleFns,
7+
LifecycleFns,
8+
updateConfig,
9+
View,
10+
} from './devsphere-config/devsphereConfigurationManager';
11+
12+
// Mock vscode module
13+
jest.mock('vscode', () => ({
14+
commands: {
15+
executeCommand: jest.fn(),
16+
},
17+
workspace: {
18+
getConfiguration: jest.fn(),
19+
},
20+
}));
21+
22+
describe('executeVSCodeCommand', () => {
23+
beforeEach(() => {
24+
jest.clearAllMocks();
25+
});
26+
27+
it('should execute command successfully', async () => {
28+
const mockExecuteCommand = vscode.commands.executeCommand as jest.Mock;
29+
mockExecuteCommand.mockResolvedValue(undefined);
30+
31+
await executeVSCodeCommand('test.command');
32+
33+
expect(mockExecuteCommand).toHaveBeenCalledWith('test.command');
34+
});
35+
36+
it('should handle command execution error and log it', async () => {
37+
const mockExecuteCommand = vscode.commands.executeCommand as jest.Mock;
38+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
39+
const error = new Error('Command failed');
40+
mockExecuteCommand.mockRejectedValue(error);
41+
42+
await executeVSCodeCommand('test.command');
43+
44+
expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to execute VS Code command: test.command', error);
45+
46+
consoleErrorSpy.mockRestore();
47+
});
48+
});
49+
50+
describe('updateConfig', () => {
51+
beforeEach(() => {
52+
jest.clearAllMocks();
53+
});
54+
55+
it('should update config when value is different', async () => {
56+
const mockGetConfiguration = vscode.workspace.getConfiguration as jest.Mock;
57+
const mockGet = jest.fn().mockReturnValue('oldValue');
58+
const mockUpdate = jest.fn().mockResolvedValue(undefined);
59+
60+
mockGetConfiguration.mockReturnValue({
61+
get: mockGet,
62+
update: mockUpdate,
63+
});
64+
65+
await updateConfig('testSection', 'testKey', 'newValue');
66+
67+
expect(mockGetConfiguration).toHaveBeenCalledWith('testSection');
68+
expect(mockGet).toHaveBeenCalledWith('testKey');
69+
expect(mockUpdate).toHaveBeenCalledWith('testKey', 'newValue', true);
70+
});
71+
72+
it('should not update config when value is the same', async () => {
73+
const mockGetConfiguration = vscode.workspace.getConfiguration as jest.Mock;
74+
const mockGet = jest.fn().mockReturnValue('sameValue');
75+
const mockUpdate = jest.fn();
76+
77+
mockGetConfiguration.mockReturnValue({
78+
get: mockGet,
79+
update: mockUpdate,
80+
});
81+
82+
await updateConfig('testSection', 'testKey', 'sameValue');
83+
84+
expect(mockGetConfiguration).toHaveBeenCalledWith('testSection');
85+
expect(mockGet).toHaveBeenCalledWith('testKey');
86+
expect(mockUpdate).not.toHaveBeenCalled();
87+
});
88+
});
89+
90+
describe('getDefaultLifecycleFns', () => {
91+
it('should return default lifecycle functions', () => {
92+
const lifecycle = getDefaultLifecycleFns();
93+
94+
expect(lifecycle).toHaveProperty('setup');
95+
expect(lifecycle).toHaveProperty('shutdown');
96+
expect(typeof lifecycle.setup).toBe('function');
97+
expect(typeof lifecycle.shutdown).toBe('function');
98+
});
99+
100+
it('should have setup function that resolves', async () => {
101+
const lifecycle = getDefaultLifecycleFns();
102+
await expect(lifecycle.setup()).resolves.toBeUndefined();
103+
});
104+
105+
it('should have shutdown function that resolves', async () => {
106+
const lifecycle = getDefaultLifecycleFns();
107+
await expect(lifecycle.shutdown()).resolves.toBeUndefined();
108+
});
109+
});
110+
111+
describe('DevsphereConfigurationManager', () => {
112+
let manager: DevsphereConfigurationManager;
113+
let mockLifecycleFns: Record<View, LifecycleFns>;
114+
115+
beforeEach(() => {
116+
jest.clearAllMocks();
117+
118+
mockLifecycleFns = {
119+
[View.Noop]: {
120+
setup: jest.fn().mockResolvedValue(undefined),
121+
shutdown: jest.fn().mockResolvedValue(undefined),
122+
},
123+
[View.Review]: {
124+
setup: jest.fn().mockResolvedValue(undefined),
125+
shutdown: jest.fn().mockResolvedValue(undefined),
126+
},
127+
};
128+
129+
manager = new DevsphereConfigurationManager(mockLifecycleFns);
130+
});
131+
132+
describe('constructor', () => {
133+
it('should create instance successfully', () => {
134+
expect(manager).toBeInstanceOf(DevsphereConfigurationManager);
135+
});
136+
});
137+
138+
describe('dispose', () => {
139+
it('should dispose and reset lifecycle functions', () => {
140+
manager.dispose();
141+
142+
// After dispose, the manager should work but with default lifecycle functions
143+
expect(manager).toBeInstanceOf(DevsphereConfigurationManager);
144+
});
145+
});
146+
147+
describe('setupView', () => {
148+
it('should setup new view and shutdown old one', async () => {
149+
await manager.setupView(View.Review);
150+
151+
expect(mockLifecycleFns[View.Noop].shutdown).toHaveBeenCalled();
152+
expect(mockLifecycleFns[View.Review].setup).toHaveBeenCalled();
153+
});
154+
155+
it('should not change view if same view is requested', async () => {
156+
await manager.setupView(View.Noop);
157+
158+
expect(mockLifecycleFns[View.Noop].shutdown).not.toHaveBeenCalled();
159+
expect(mockLifecycleFns[View.Noop].setup).not.toHaveBeenCalled();
160+
});
161+
162+
it('should handle shutdown error gracefully', async () => {
163+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
164+
const shutdownError = new Error('Shutdown failed');
165+
(mockLifecycleFns[View.Noop].shutdown as jest.Mock).mockRejectedValue(shutdownError);
166+
167+
await manager.setupView(View.Review);
168+
169+
expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to shutdown view: noop', shutdownError);
170+
expect(mockLifecycleFns[View.Review].setup).toHaveBeenCalled();
171+
172+
consoleErrorSpy.mockRestore();
173+
});
174+
175+
it('should handle setup error gracefully', async () => {
176+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
177+
const setupError = new Error('Setup failed');
178+
(mockLifecycleFns[View.Review].setup as jest.Mock).mockRejectedValue(setupError);
179+
180+
await manager.setupView(View.Review);
181+
182+
expect(mockLifecycleFns[View.Noop].shutdown).toHaveBeenCalled();
183+
expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to setup view: review', setupError);
184+
185+
consoleErrorSpy.mockRestore();
186+
});
187+
});
188+
});

0 commit comments

Comments
 (0)