Skip to content

Commit c7d1adb

Browse files
committed
test: add parsePackFile exec tests
1 parent 38915e7 commit c7d1adb

File tree

1 file changed

+324
-0
lines changed

1 file changed

+324
-0
lines changed

test/testParsePush.test.js

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
const { expect } = require('chai');
2+
const sinon = require('sinon');
3+
const zlib = require('zlib');
4+
5+
const {
6+
exec,
7+
getCommitData,
8+
getPackMeta,
9+
unpack
10+
} = require('../src/proxy/processors/push-action/parsePush');
11+
12+
function createSamplePackBuffer(numEntries = 1, commitContent = 'tree 123\nparent 456\nauthor A <a@a> 123 +0000\ncommitter C <c@c> 456 +0000\n\nmessage', type = 1) {
13+
const header = Buffer.alloc(12);
14+
header.write('PACK', 0, 4, 'utf-8'); // Signature
15+
header.writeUInt32BE(2, 4); // Version
16+
header.writeUInt32BE(numEntries, 8); // Number of entries
17+
18+
const originalContent = Buffer.from(commitContent, 'utf8');
19+
// Actual zlib used for setup
20+
const compressedContent = zlib.deflateSync(originalContent);
21+
22+
// Basic type/size encoding (assumes small sizes for simplicity)
23+
// Real PACK files use variable-length encoding for size
24+
let typeAndSize = (type << 4) | (compressedContent.length & 0x0f); // Lower 4 bits of size
25+
if (compressedContent.length >= 16) {
26+
typeAndSize |= 0x80;
27+
}
28+
const objectHeader = Buffer.from([typeAndSize]); // Placeholder, actual size encoding is complex
29+
30+
// Combine parts
31+
const packContent = Buffer.concat([objectHeader, compressedContent]);
32+
33+
// Append checksum (dummy 20 bytes)
34+
const checksum = Buffer.alloc(20);
35+
36+
return Buffer.concat([header, packContent, checksum]);
37+
}
38+
39+
function createPacketLineBuffer(lines) {
40+
let buffer = Buffer.alloc(0);
41+
lines.forEach(line => {
42+
const lengthInHex = (line.length + 4).toString(16).padStart(4, '0');
43+
buffer = Buffer.concat([buffer, Buffer.from(lengthInHex, 'ascii'), Buffer.from(line, 'ascii')]);
44+
});
45+
buffer = Buffer.concat([buffer, Buffer.from('0000', 'ascii')]);
46+
47+
return buffer;
48+
}
49+
50+
describe('parsePackFile', () => {
51+
let action;
52+
let req;
53+
let sandbox;
54+
let zlibInflateStub; // No deflate stub used due to complexity of PACK encoding
55+
56+
beforeEach(() => {
57+
sandbox = sinon.createSandbox();
58+
59+
// Mock Action and Step and spy on methods
60+
action = {
61+
branch: null,
62+
commitFrom: null,
63+
commitTo: null,
64+
commitData: [],
65+
user: null,
66+
steps: [],
67+
addStep: sandbox.spy(function (step) {
68+
this.steps.push(step); // eslint-disable-line no-invalid-this
69+
}),
70+
setCommit: sandbox.spy(function (from, to) {
71+
this.commitFrom = from; // eslint-disable-line no-invalid-this
72+
this.commitTo = to; // eslint-disable-line no-invalid-this
73+
}),
74+
};
75+
76+
req = {
77+
body: null,
78+
};
79+
80+
zlibInflateStub = sandbox.stub(zlib, 'inflateSync');
81+
});
82+
83+
afterEach(() => {
84+
sandbox.restore();
85+
});
86+
87+
describe('exec', () => {
88+
it('should add error step if req.body is missing', async () => {
89+
req.body = undefined;
90+
const result = await exec(req, action);
91+
92+
expect(result).to.equal(action);
93+
const step = action.steps[0];
94+
expect(step.stepName).to.equal('parsePackFile');
95+
expect(step.error).to.be.true;
96+
expect(step.errorMessage).to.include('No data received');
97+
});
98+
99+
it('should add error step if req.body is empty', async () => {
100+
req.body = Buffer.alloc(0);
101+
const result = await exec(req, action);
102+
103+
expect(result).to.equal(action);
104+
const step = action.steps[0];
105+
expect(step.stepName).to.equal('parsePackFile');
106+
expect(step.error).to.be.true;
107+
expect(step.errorMessage).to.include('No data received');
108+
});
109+
110+
it('should add error step if no ref updates found', async () => {
111+
const packetLines = ['some other line\n', 'another line\n'];
112+
req.body = createPacketLineBuffer(packetLines); // We don't include PACK data (only testing ref updates)
113+
const result = await exec(req, action);
114+
115+
expect(result).to.equal(action);
116+
const step = action.steps[0];
117+
expect(step.stepName).to.equal('parsePackFile');
118+
expect(step.error).to.be.true;
119+
expect(step.errorMessage).to.include('pushing to a single branch');
120+
expect(step.logs[0]).to.include('Invalid number of branch updates');
121+
});
122+
123+
it('should add error step if multiple ref updates found', async () => {
124+
const packetLines = [
125+
'oldhash1 newhash1 refs/heads/main\0caps\n',
126+
'oldhash2 newhash2 refs/heads/develop\0caps\n',
127+
];
128+
req.body = createPacketLineBuffer(packetLines);
129+
const result = await exec(req, action);
130+
131+
expect(result).to.equal(action);
132+
const step = action.steps[0];
133+
expect(step.stepName).to.equal('parsePackFile');
134+
expect(step.error).to.be.true;
135+
expect(step.errorMessage).to.include('pushing to a single branch');
136+
expect(step.logs[0]).to.include('Invalid number of branch updates');
137+
expect(step.logs[1]).to.include('Expected 1, but got 2');
138+
});
139+
140+
it('should add error step if PACK data is missing', async () => {
141+
const oldCommit = 'a'.repeat(40);
142+
const newCommit = 'b'.repeat(40);
143+
const ref = 'refs/heads/feature/test';
144+
const packetLines = [`${oldCommit} ${newCommit} ${ref}\0capa\n`];
145+
146+
req.body = createPacketLineBuffer(packetLines);
147+
148+
const result = await exec(req, action);
149+
150+
expect(result).to.equal(action);
151+
const step = action.steps[0];
152+
expect(step.stepName).to.equal('parsePackFile');
153+
expect(step.error).to.be.true;
154+
expect(step.errorMessage).to.include('Unable to parse push');
155+
156+
expect(action.branch).to.equal(ref);
157+
expect(action.setCommit.calledOnceWith(oldCommit, newCommit)).to.be.true;
158+
});
159+
160+
it('should successfully parse a valid push request', async () => {
161+
const oldCommit = 'a'.repeat(40);
162+
const newCommit = 'b'.repeat(40);
163+
const ref = 'refs/heads/main';
164+
const packetLine = `${oldCommit} ${newCommit} ${ref}\0capabilities\n`;
165+
166+
const commitContent = `tree 1234567890abcdef1234567890abcdef12345678
167+
parent abcdef1234567890abcdef1234567890abcdef12
168+
author Test Author <[email protected]> 1678886400 +0000
169+
committer Test Committer <[email protected]> 1678886460 +0100
170+
171+
feat: Add new feature
172+
173+
This is the commit body.`;
174+
const commitContentBuffer = Buffer.from(commitContent, 'utf8');
175+
176+
zlibInflateStub.returns(commitContentBuffer);
177+
178+
const numEntries = 1;
179+
const packBuffer = createSamplePackBuffer(numEntries, commitContent, 1); // Use real zlib
180+
req.body = Buffer.concat([createPacketLineBuffer([packetLine]), packBuffer]);
181+
182+
const result = await exec(req, action);
183+
expect(result).to.equal(action);
184+
185+
// Check step and action properties
186+
const step = action.steps.find(s => s.stepName === 'parsePackFile');
187+
expect(step).to.exist;
188+
expect(step.error).to.be.false;
189+
expect(step.errorMessage).to.be.null;
190+
191+
expect(action.branch).to.equal(ref);
192+
expect(action.setCommit.calledOnceWith(oldCommit, newCommit)).to.be.true;
193+
expect(action.commitFrom).to.equal(oldCommit);
194+
expect(action.commitTo).to.equal(newCommit);
195+
expect(action.user).to.equal('Test Committer');
196+
197+
// Check parsed commit data
198+
const commitMessages = action.commitData.map(commit => commit.message);
199+
expect(action.commitData).to.be.an('array').with.lengthOf(1);
200+
expect(commitMessages[0]).to.equal('feat: Add new feature\n\nThis is the commit body.');
201+
202+
const parsedCommit = action.commitData[0];
203+
expect(parsedCommit.tree).to.equal('1234567890abcdef1234567890abcdef12345678');
204+
expect(parsedCommit.parent).to.equal('abcdef1234567890abcdef1234567890abcdef12');
205+
expect(parsedCommit.author).to.equal('Test Author');
206+
expect(parsedCommit.committer).to.equal('Test Committer');
207+
expect(parsedCommit.commitTimestamp).to.equal('1678886460');
208+
expect(parsedCommit.message).to.equal('feat: Add new feature\n\nThis is the commit body.');
209+
expect(parsedCommit.authorEmail).to.equal('[email protected]');
210+
211+
expect(step.content.meta).to.deep.equal({
212+
sig: 'PACK',
213+
version: 2,
214+
entries: numEntries,
215+
});
216+
});
217+
218+
it('should handle initial commit (zero hash oldCommit)', async () => {
219+
const oldCommit = '0'.repeat(40); // Zero hash
220+
const newCommit = 'b'.repeat(40);
221+
const ref = 'refs/heads/main';
222+
const packetLine = `${oldCommit} ${newCommit} ${ref}\0capabilities\n`;
223+
224+
// Commit content without a parent line
225+
const commitContent = `tree 1234567890abcdef1234567890abcdef12345678
226+
author Test Author <[email protected]> 1678886400 +0000
227+
committer Test Committer <[email protected]> 1678886460 +0100
228+
229+
feat: Initial commit`;
230+
const parentFromCommit = '0'.repeat(40); // Expected parent hash
231+
232+
const commitContentBuffer = Buffer.from(commitContent, 'utf8');
233+
zlibInflateStub.returns(commitContentBuffer);
234+
235+
const packBuffer = createSamplePackBuffer(1, commitContent, 1); // Use real zlib
236+
req.body = Buffer.concat([createPacketLineBuffer([packetLine]), packBuffer]);
237+
238+
const result = await exec(req, action);
239+
240+
expect(result).to.equal(action);
241+
const step = action.steps.find(s => s.stepName === 'parsePackFile');
242+
expect(step).to.exist;
243+
expect(step.error).to.be.false;
244+
245+
expect(action.branch).to.equal(ref);
246+
expect(action.setCommit.calledOnceWith(oldCommit, newCommit)).to.be.true;
247+
248+
// commitFrom should still be the zero hash
249+
expect(action.commitFrom).to.equal(oldCommit);
250+
expect(action.commitTo).to.equal(newCommit);
251+
expect(action.user).to.equal('Test Committer');
252+
253+
// Check parsed commit data reflects no parent (zero hash)
254+
expect(action.commitData[0].parent).to.equal(parentFromCommit);
255+
});
256+
257+
it('should handle commit with multiple parents (merge commit)', async () => {
258+
const oldCommit = 'a'.repeat(40);
259+
const newCommit = 'c'.repeat(40); // Merge commit hash
260+
const ref = 'refs/heads/main';
261+
const packetLine = `${oldCommit} ${newCommit} ${ref}\0capabilities\n`;
262+
263+
const parent1 = 'b1'.repeat(20);
264+
const parent2 = 'b2'.repeat(20);
265+
const commitContent = `tree 1234567890abcdef1234567890abcdef12345678
266+
parent ${parent1}
267+
parent ${parent2}
268+
author Test Author <[email protected]> 1678886400 +0000
269+
committer Test Committer <[email protected]> 1678886460 +0100
270+
271+
Merge branch 'feature'`;
272+
273+
const commitContentBuffer = Buffer.from(commitContent, 'utf8');
274+
zlibInflateStub.returns(commitContentBuffer);
275+
276+
const packBuffer = createSamplePackBuffer(1, commitContent, 1); // Use real zlib
277+
req.body = Buffer.concat([createPacketLineBuffer([packetLine]), packBuffer]);
278+
279+
const result = await exec(req, action);
280+
expect(result).to.equal(action);
281+
282+
// Check step and action properties
283+
const step = action.steps.find(s => s.stepName === 'parsePackFile');
284+
expect(step).to.exist;
285+
expect(step.error).to.be.false;
286+
287+
expect(action.branch).to.equal(ref);
288+
expect(action.setCommit.calledOnceWith(oldCommit, newCommit)).to.be.true;
289+
expect(action.commitFrom).to.equal(oldCommit);
290+
expect(action.commitTo).to.equal(newCommit);
291+
292+
// Parent should be the FIRST parent in the commit content
293+
expect(action.commitData[0].parent).to.equal(parent1);
294+
});
295+
296+
it('should add error step if getCommitData throws error', async () => {
297+
const oldCommit = 'a'.repeat(40);
298+
const newCommit = 'b'.repeat(40);
299+
const ref = 'refs/heads/main';
300+
const packetLine = `${oldCommit} ${newCommit} ${ref}\0capabilities\n`;
301+
302+
// Malformed commit content - missing tree line
303+
const commitContent = `parent abcdef1234567890abcdef1234567890abcdef12
304+
author Test Author <[email protected]> 1678886400 +0000
305+
committer Test Committer <[email protected]> 1678886460 +0100
306+
307+
feat: Missing tree`;
308+
const commitContentBuffer = Buffer.from(commitContent, 'utf8');
309+
zlibInflateStub.returns(commitContentBuffer);
310+
311+
const packBuffer = createSamplePackBuffer(1, commitContent, 1);
312+
req.body = Buffer.concat([createPacketLineBuffer([packetLine]), packBuffer]);
313+
314+
const result = await exec(req, action);
315+
expect(result).to.equal(action);
316+
317+
const step = action.steps.find(s => s.stepName === 'parsePackFile');
318+
expect(step).to.exist;
319+
expect(step.error).to.be.true;
320+
expect(step.errorMessage).to.include('Invalid commit data: Missing tree');
321+
});
322+
});
323+
324+
});

0 commit comments

Comments
 (0)