Skip to content

Commit 2404074

Browse files
committed
test(ci): add integration tests for custom monorepo setups
1 parent d0f94d7 commit 2404074

File tree

5 files changed

+264
-1
lines changed

5 files changed

+264
-1
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "api",
3+
"devDependencies": {
4+
"@code-pushup/cli": "latest"
5+
}
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "auth",
3+
"devDependencies": {
4+
"@code-pushup/cli": "latest"
5+
}
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "frontend",
3+
"devDependencies": {
4+
"@code-pushup/cli": "latest"
5+
}
6+
}

packages/ci/src/lib/run.integration.test.ts

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,4 +698,249 @@ describe('runInCI', () => {
698698
});
699699
});
700700
});
701+
702+
describe.each<[string, Options]>([
703+
[
704+
'projects explicitly configured using folder patterns',
705+
{
706+
monorepo: true,
707+
projects: ['frontend', 'backend/*'],
708+
},
709+
],
710+
[
711+
'projects implicitly determined by package.json files',
712+
{
713+
monorepo: true,
714+
},
715+
],
716+
])('monorepo mode - custom: %s', (_, monorepoOptions) => {
717+
beforeEach(async () => {
718+
const monorepoDir = join(fixturesDir, 'monorepos', 'custom');
719+
await cp(monorepoDir, workDir, { recursive: true });
720+
await git.add('.');
721+
await git.commit('Create projects in monorepo');
722+
});
723+
724+
describe('push event', () => {
725+
beforeEach(async () => {
726+
await git.checkout('main');
727+
});
728+
729+
it('should collect reports for all projects', async () => {
730+
await expect(
731+
runInCI(
732+
{ head: { ref: 'main', sha: await git.revparse('main') } },
733+
{} as ProviderAPIClient,
734+
{ ...options, ...monorepoOptions },
735+
git,
736+
),
737+
).resolves.toEqual({
738+
mode: 'monorepo',
739+
projects: [
740+
{
741+
name: expect.stringContaining('api'),
742+
files: {
743+
report: {
744+
json: join(workDir, 'backend/api/.code-pushup/report.json'),
745+
md: join(workDir, 'backend/api/.code-pushup/report.md'),
746+
},
747+
},
748+
},
749+
{
750+
name: expect.stringContaining('auth'),
751+
files: {
752+
report: {
753+
json: join(workDir, 'backend/auth/.code-pushup/report.json'),
754+
md: join(workDir, 'backend/auth/.code-pushup/report.md'),
755+
},
756+
},
757+
},
758+
{
759+
name: 'frontend',
760+
files: {
761+
report: {
762+
json: join(workDir, 'frontend/.code-pushup/report.json'),
763+
md: join(workDir, 'frontend/.code-pushup/report.md'),
764+
},
765+
},
766+
},
767+
],
768+
} satisfies RunResult);
769+
770+
expect(
771+
executeProcessSpy.mock.calls.filter(([cfg]) =>
772+
cfg.command.includes('code-pushup'),
773+
),
774+
).toHaveLength(6); // 3 autoruns and 3 print-configs for each project
775+
expect(utils.executeProcess).toHaveBeenCalledWith({
776+
command: options.bin,
777+
args: ['print-config'],
778+
cwd: expect.stringContaining(workDir),
779+
} satisfies utils.ProcessConfig);
780+
expect(utils.executeProcess).toHaveBeenCalledWith({
781+
command: options.bin,
782+
args: ['--persist.format=json', '--persist.format=md'],
783+
cwd: expect.stringContaining(workDir),
784+
} satisfies utils.ProcessConfig);
785+
786+
expect(logger.error).not.toHaveBeenCalled();
787+
expect(logger.warn).not.toHaveBeenCalled();
788+
expect(logger.info).toHaveBeenCalled();
789+
expect(logger.debug).toHaveBeenCalled();
790+
});
791+
});
792+
793+
describe('pull request event', () => {
794+
let refs: GitRefs;
795+
let diffMdString: string;
796+
797+
beforeEach(async () => {
798+
await git.checkoutLocalBranch('feature-1');
799+
800+
await writeFile(join(workDir, 'README.md'), '# Hello, world\n');
801+
await git.add('README.md');
802+
await git.commit('Create README');
803+
804+
refs = {
805+
head: { ref: 'feature-1', sha: await git.revparse('feature-1') },
806+
base: { ref: 'main', sha: await git.revparse('main') },
807+
};
808+
809+
diffMdString = await readFile(fixturePaths.diffs.merged.md, 'utf8');
810+
});
811+
812+
it('should collect and compare reports for all projects and comment merged diff', async () => {
813+
const api: ProviderAPIClient = {
814+
maxCommentChars: 1_000_000,
815+
createComment: vi.fn().mockResolvedValue(mockComment),
816+
updateComment: vi.fn(),
817+
listComments: vi.fn().mockResolvedValue([]),
818+
downloadReportArtifact: vi.fn().mockImplementation(async project => {
819+
const downloadPath = join(workDir, 'tmp', project, 'report.json');
820+
await mkdir(dirname(downloadPath), { recursive: true });
821+
await copyFile(fixturePaths.reports.before.json, downloadPath);
822+
return downloadPath;
823+
}),
824+
};
825+
826+
await expect(
827+
runInCI(refs, api, { ...options, ...monorepoOptions }, git),
828+
).resolves.toEqual({
829+
mode: 'monorepo',
830+
commentId: mockComment.id,
831+
diffPath: join(workDir, '.code-pushup/merged-report-diff.md'),
832+
projects: [
833+
{
834+
name: expect.stringContaining('api'),
835+
files: {
836+
report: {
837+
json: join(workDir, 'backend/api/.code-pushup/report.json'),
838+
md: join(workDir, 'backend/api/.code-pushup/report.md'),
839+
},
840+
diff: {
841+
json: join(
842+
workDir,
843+
'backend/api/.code-pushup/report-diff.json',
844+
),
845+
md: join(workDir, 'backend/api/.code-pushup/report-diff.md'),
846+
},
847+
},
848+
newIssues: [],
849+
},
850+
{
851+
name: expect.stringContaining('auth'),
852+
files: {
853+
report: {
854+
json: join(workDir, 'backend/auth/.code-pushup/report.json'),
855+
md: join(workDir, 'backend/auth/.code-pushup/report.md'),
856+
},
857+
diff: {
858+
json: join(
859+
workDir,
860+
'backend/auth/.code-pushup/report-diff.json',
861+
),
862+
md: join(workDir, 'backend/auth/.code-pushup/report-diff.md'),
863+
},
864+
},
865+
newIssues: [],
866+
},
867+
{
868+
name: 'frontend',
869+
files: {
870+
report: {
871+
json: join(workDir, 'frontend/.code-pushup/report.json'),
872+
md: join(workDir, 'frontend/.code-pushup/report.md'),
873+
},
874+
diff: {
875+
json: join(workDir, 'frontend/.code-pushup/report-diff.json'),
876+
md: join(workDir, 'frontend/.code-pushup/report-diff.md'),
877+
},
878+
},
879+
newIssues: [],
880+
},
881+
],
882+
} satisfies RunResult);
883+
884+
await expect(
885+
readFile(join(workDir, '.code-pushup/merged-report-diff.md'), 'utf8'),
886+
).resolves.toBe(diffMdString);
887+
888+
expect(api.listComments).toHaveBeenCalledWith();
889+
expect(api.createComment).toHaveBeenCalledWith(
890+
expect.stringContaining(diffMdString),
891+
);
892+
expect(api.updateComment).not.toHaveBeenCalled();
893+
894+
// 3 autoruns for each project
895+
// 3 print-configs for each project
896+
// 3 compares for each project
897+
// 0 autoruns and print-configs for uncached projects
898+
// 1 merge-diffs for all projects
899+
expect(
900+
executeProcessSpy.mock.calls.filter(([cfg]) =>
901+
cfg.command.includes('code-pushup'),
902+
),
903+
).toHaveLength(10);
904+
expect(utils.executeProcess).toHaveBeenCalledWith({
905+
command: options.bin,
906+
args: ['print-config'],
907+
cwd: expect.stringContaining(workDir),
908+
} satisfies utils.ProcessConfig);
909+
expect(utils.executeProcess).toHaveBeenCalledWith({
910+
command: options.bin,
911+
args: ['--persist.format=json', '--persist.format=md'],
912+
cwd: expect.stringContaining(workDir),
913+
} satisfies utils.ProcessConfig);
914+
expect(utils.executeProcess).toHaveBeenCalledWith({
915+
command: options.bin,
916+
args: [
917+
'compare',
918+
expect.stringMatching(/^--before=.*prev-report.json$/),
919+
expect.stringMatching(/^--after=.*curr-report.json$/),
920+
expect.stringMatching(/^--label=\w+$/),
921+
'--persist.format=json',
922+
'--persist.format=md',
923+
],
924+
cwd: expect.stringContaining(workDir),
925+
} satisfies utils.ProcessConfig);
926+
expect(utils.executeProcess).toHaveBeenCalledWith({
927+
command: options.bin,
928+
args: [
929+
'merge-diffs',
930+
`--files=${join(workDir, 'backend/api/.code-pushup/report-diff.json')}`,
931+
`--files=${join(workDir, 'backend/auth/.code-pushup/report-diff.json')}`,
932+
`--files=${join(workDir, 'frontend/.code-pushup/report-diff.json')}`,
933+
expect.stringMatching(/^--persist.outputDir=.*\.code-pushup$/),
934+
'--persist.filename=merged-report',
935+
],
936+
cwd: expect.stringContaining(workDir),
937+
} satisfies utils.ProcessConfig);
938+
939+
expect(logger.error).not.toHaveBeenCalled();
940+
expect(logger.warn).not.toHaveBeenCalled();
941+
expect(logger.info).toHaveBeenCalled();
942+
expect(logger.debug).toHaveBeenCalled();
943+
});
944+
});
945+
});
701946
});

packages/ci/src/lib/run.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ async function runInMonorepoMode(env: RunEnv): Promise<RunResult> {
125125
path.basename(tmpDiffPath),
126126
);
127127
if (tmpDiffPath !== diffPath) {
128-
await fs.cp(tmpDiffPath, diffPath);
128+
await fs.copyFile(tmpDiffPath, diffPath);
129129
logger.debug(`Copied ${tmpDiffPath} to ${diffPath}`);
130130
}
131131
const commentId = await commentOnPR(tmpDiffPath, api, logger);

0 commit comments

Comments
 (0)