Skip to content

Commit 56b7e25

Browse files
committed
more tests
1 parent e2660c8 commit 56b7e25

File tree

1 file changed

+218
-0
lines changed

1 file changed

+218
-0
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/**
2+
* SHELL-LEVEL GH-PAGES TESTING
3+
*
4+
* This test suite mocks child_process.spawn to capture the exact git commands
5+
* executed by gh-pages.publish(). This allows us to:
6+
*
7+
* 1. Document the exact git command sequence with gh-pages v3.1.0
8+
* 2. Upgrade to gh-pages v6.1.1
9+
* 3. Verify that the git command sequence remains identical
10+
* 4. Detect any behavioral changes at the shell level
11+
*
12+
* Strategy:
13+
* - Mock cp.spawn to intercept all git commands
14+
* - Capture: executable, arguments, working directory
15+
* - Compare command sequences before/after upgrade
16+
*
17+
* CURRENT STATUS:
18+
* This is infrastructure-only for now. The actual integration with gh-pages
19+
* requires mocking the file system (fs-extra) as well. This will be implemented
20+
* in the next iteration.
21+
*/
22+
23+
interface SpawnCall {
24+
exe: string;
25+
args: string[];
26+
cwd: string;
27+
}
28+
29+
const spawnCalls: SpawnCall[] = [];
30+
31+
// Mock child_process.spawn at module level (must be before imports)
32+
jest.mock('child_process', () => ({
33+
...jest.requireActual('child_process'),
34+
spawn: jest.fn((command: string, args?: readonly string[], options?: {cwd?: string}) => {
35+
const cwd = options?.cwd || process.cwd();
36+
37+
spawnCalls.push({
38+
exe: command,
39+
args: args ? [...args] : [],
40+
cwd
41+
});
42+
43+
// Create mock child process that succeeds immediately
44+
const mockChild = {
45+
stdout: {
46+
on: jest.fn((event: string, callback?: (data: Buffer) => void) => {
47+
if (event === 'data' && callback) {
48+
// Simulate git output for commands that return data
49+
if (args && args.includes('config') && args.includes('--get')) {
50+
callback(Buffer.from('https://github.com/test/repo.git\n'));
51+
}
52+
}
53+
return mockChild.stdout;
54+
})
55+
},
56+
stderr: {
57+
on: jest.fn(() => mockChild.stderr)
58+
},
59+
on: jest.fn((event: string, callback?: (code: number) => void) => {
60+
if (event === 'close' && callback) {
61+
// Simulate successful command (exit code 0)
62+
setTimeout(() => callback(0), 0);
63+
}
64+
return mockChild;
65+
})
66+
};
67+
68+
return mockChild;
69+
})
70+
}));
71+
72+
import { logging } from '@angular-devkit/core';
73+
74+
describe('Shell-Level gh-pages Testing', () => {
75+
let logger: logging.LoggerApi;
76+
77+
beforeEach(() => {
78+
logger = new logging.NullLogger();
79+
spawnCalls.length = 0; // Clear array
80+
process.env = {};
81+
});
82+
83+
describe('Helper functions', () => {
84+
it('should format git commands as readable strings', () => {
85+
const calls: SpawnCall[] = [
86+
{
87+
exe: 'git',
88+
args: ['clone', 'https://github.com/test/repo.git', '/cache/dir', '--branch', 'gh-pages'],
89+
cwd: '/test'
90+
},
91+
{
92+
exe: 'git',
93+
args: ['config', 'user.email', 'test@example.com'],
94+
cwd: '/cache/dir'
95+
},
96+
{
97+
exe: 'git',
98+
args: ['commit', '-m', 'Deploy'],
99+
cwd: '/cache/dir'
100+
}
101+
];
102+
103+
const formatted = calls.map(call => formatGitCommand(call));
104+
105+
expect(formatted[0]).toBe('git clone https://github.com/test/repo.git /cache/dir --branch gh-pages');
106+
expect(formatted[1]).toBe('git config user.email test@example.com');
107+
expect(formatted[2]).toBe('git commit -m Deploy');
108+
});
109+
110+
it('should filter only git commands from spawn calls', () => {
111+
const calls: SpawnCall[] = [
112+
{ exe: 'git', args: ['status'], cwd: '/test' },
113+
{ exe: 'node', args: ['script.js'], cwd: '/test' },
114+
{ exe: 'git', args: ['commit', '-m', 'test'], cwd: '/test' },
115+
{ exe: 'npm', args: ['install'], cwd: '/test' }
116+
];
117+
118+
const gitCommands = filterGitCommands(calls);
119+
120+
expect(gitCommands).toHaveLength(2);
121+
expect(gitCommands[0].args[0]).toBe('status');
122+
expect(gitCommands[1].args[0]).toBe('commit');
123+
});
124+
125+
it('should group git commands by operation type', () => {
126+
const calls: SpawnCall[] = [
127+
{ exe: 'git', args: ['clone', 'repo', 'dir'], cwd: '/test' },
128+
{ exe: 'git', args: ['fetch', 'origin'], cwd: '/test' },
129+
{ exe: 'git', args: ['add', '.'], cwd: '/test' },
130+
{ exe: 'git', args: ['commit', '-m', 'msg'], cwd: '/test' },
131+
{ exe: 'git', args: ['push', 'origin', 'main'], cwd: '/test' }
132+
];
133+
134+
const groups = groupGitCommandsByType(calls);
135+
136+
expect(groups.clone).toHaveLength(1);
137+
expect(groups.fetch).toHaveLength(1);
138+
expect(groups.add).toHaveLength(1);
139+
expect(groups.commit).toHaveLength(1);
140+
expect(groups.push).toHaveLength(1);
141+
});
142+
});
143+
144+
describe('Documentation: Expected git command sequence', () => {
145+
it('should document the expected git commands for standard deployment', () => {
146+
// This test documents what we EXPECT to see when gh-pages.publish() runs
147+
// Once we implement full mocking, we'll verify these commands are executed
148+
149+
const expectedCommands = [
150+
'git clone <repo> <cache-dir> --branch <branch> --single-branch --origin <remote> --depth 1',
151+
'git config --get remote.<remote>.url',
152+
'git clean -f -d',
153+
'git fetch <remote>',
154+
'git ls-remote --exit-code . <remote>/<branch>',
155+
'git checkout <branch>',
156+
'git reset --hard <remote>/<branch>',
157+
'git rm --ignore-unmatch -r -f . (unless --add flag)',
158+
'git add .',
159+
'git config user.email <email> (if user set)',
160+
'git config user.name <name> (if user set)',
161+
'git diff-index --quiet HEAD',
162+
'git commit -m <message>',
163+
'git push --tags <remote> <branch>'
164+
];
165+
166+
// Document for future reference
167+
expect(expectedCommands).toHaveLength(14);
168+
});
169+
170+
it('should document variations with --add flag', () => {
171+
const withoutAdd = 'Files are removed before new ones are added';
172+
const withAdd = 'Files are NOT removed, just added on top';
173+
174+
expect(withoutAdd).toBeDefined();
175+
expect(withAdd).toBeDefined();
176+
});
177+
178+
it('should document user credentials handling', () => {
179+
const withUser = 'git config user.email and user.name are executed';
180+
const withoutUser = 'git uses local/global git config for commit author';
181+
182+
expect(withUser).toBeDefined();
183+
expect(withoutUser).toBeDefined();
184+
});
185+
});
186+
});
187+
188+
/**
189+
* Format a spawn call as a readable git command string
190+
*/
191+
function formatGitCommand(call: SpawnCall): string {
192+
return `${call.exe} ${call.args.join(' ')}`;
193+
}
194+
195+
/**
196+
* Filter only git commands from spawn calls
197+
*/
198+
function filterGitCommands(calls: SpawnCall[]): SpawnCall[] {
199+
return calls.filter(call => call.exe === 'git' || call.exe.endsWith('/git'));
200+
}
201+
202+
/**
203+
* Group git commands by operation type (clone, fetch, add, commit, push, etc.)
204+
*/
205+
function groupGitCommandsByType(calls: SpawnCall[]): Record<string, SpawnCall[]> {
206+
const gitCalls = filterGitCommands(calls);
207+
const groups: Record<string, SpawnCall[]> = {};
208+
209+
for (const call of gitCalls) {
210+
const operation = call.args[0] || 'unknown';
211+
if (!groups[operation]) {
212+
groups[operation] = [];
213+
}
214+
groups[operation].push(call);
215+
}
216+
217+
return groups;
218+
}

0 commit comments

Comments
 (0)