Skip to content

Commit 8723686

Browse files
authored
refactor: git handler (#1211)
## 🧰 Changes `rdme docs upload` is going to be checking for remote URLs soon and the logic around our usage of `simple-git` is a bit messy at the moment. this PR moves some things around and consolidates a bunch of poorly-named test helper files into a single `git-mock.ts` test helper. right now this change is being made against the `v9` branch and i'll be forward-porting it to `next` once everything looks good. ## 🧬 QA & Testing do tests still pass?
1 parent 8f17346 commit 8723686

14 files changed

+222
-222
lines changed

__tests__/commands/docs/index.test.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@ import { describe, beforeAll, afterAll, beforeEach, afterEach, it, expect, vi, t
1212
import Command from '../../../src/commands/docs/index.js';
1313
import { APIv1Error } from '../../../src/lib/apiError.js';
1414
import { getAPIv1Mock, getAPIv1MockWithVersionHeader } from '../../helpers/get-api-mock.js';
15-
import { after, before } from '../../helpers/get-gha-setup.js';
15+
import { gitMock, githubActionsEnv } from '../../helpers/git-mock.js';
1616
import hashFileContents from '../../helpers/hash-file-contents.js';
1717
import { runCommandAndReturnResult, runCommandWithHooks } from '../../helpers/oclif.js';
18-
import { after as afterGHAEnv, before as beforeGHAEnv } from '../../helpers/setup-gha-env.js';
1918

2019
const fixturesBaseDir = '__fixtures__/docs';
2120
const fullFixturesDir = `${__dirname}./../../${fixturesBaseDir}`;
@@ -405,13 +404,13 @@ describe('rdme docs', () => {
405404
beforeEach(() => {
406405
consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation(() => {});
407406

408-
before((fileName, data) => {
407+
gitMock.before((fileName, data) => {
409408
yamlOutput = data;
410409
});
411410
});
412411

413412
afterEach(() => {
414-
after();
413+
gitMock.after();
415414

416415
consoleInfoSpy.mockRestore();
417416
});
@@ -589,10 +588,12 @@ describe('rdme docs', () => {
589588

590589
describe('command execution in GitHub Actions runner', () => {
591590
beforeEach(() => {
592-
beforeGHAEnv();
591+
githubActionsEnv.before();
593592
});
594593

595-
afterEach(afterGHAEnv);
594+
afterEach(() => {
595+
githubActionsEnv.after();
596+
});
596597

597598
it('should sync new docs directory with correct headers', async () => {
598599
const slug = 'new-doc';

__tests__/commands/docs/single.test.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import { describe, beforeAll, afterAll, beforeEach, afterEach, it, expect } from
99
import Command from '../../../src/commands/docs/index.js';
1010
import { APIv1Error } from '../../../src/lib/apiError.js';
1111
import { getAPIv1Mock, getAPIv1MockWithVersionHeader } from '../../helpers/get-api-mock.js';
12+
import { githubActionsEnv } from '../../helpers/git-mock.js';
1213
import hashFileContents from '../../helpers/hash-file-contents.js';
1314
import { runCommandAndReturnResult } from '../../helpers/oclif.js';
14-
import { after as afterGHAEnv, before as beforeGHAEnv } from '../../helpers/setup-gha-env.js';
1515

1616
const fixturesBaseDir = '__fixtures__/docs';
1717
const fullFixturesDir = `${__dirname}./../../${fixturesBaseDir}`;
@@ -344,10 +344,12 @@ describe('rdme docs (single)', () => {
344344

345345
describe('command execution in GitHub Actions runner', () => {
346346
beforeEach(() => {
347-
beforeGHAEnv();
347+
githubActionsEnv.before();
348348
});
349349

350-
afterEach(afterGHAEnv);
350+
afterEach(() => {
351+
githubActionsEnv.after();
352+
});
351353

352354
it('should sync new doc with correct headers', async () => {
353355
const slug = 'new-doc';

__tests__/commands/openapi/index.test.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ import { APIv1Error } from '../../../src/lib/apiError.js';
1212
import config from '../../../src/lib/config.js';
1313
import petstoreWeird from '../../__fixtures__/petstore-simple-weird-version.json' with { type: 'json' };
1414
import { getAPIv1Mock, getAPIv1MockWithVersionHeader } from '../../helpers/get-api-mock.js';
15-
import { after, before } from '../../helpers/get-gha-setup.js';
15+
import { githubActionsEnv, gitMock } from '../../helpers/git-mock.js';
1616
import { runCommandAndReturnResult } from '../../helpers/oclif.js';
17-
import { after as afterGHAEnv, before as beforeGHAEnv } from '../../helpers/setup-gha-env.js';
1817

1918
let consoleInfoSpy: MockInstance;
2019
let consoleWarnSpy: MockInstance;
@@ -1044,13 +1043,13 @@ describe('rdme openapi', () => {
10441043
let yamlOutput;
10451044

10461045
beforeEach(() => {
1047-
before((fileName, data) => {
1046+
gitMock.before((fileName, data) => {
10481047
yamlOutput = data;
10491048
});
10501049
});
10511050

10521051
afterEach(() => {
1053-
after();
1052+
gitMock.after();
10541053
});
10551054

10561055
it('should create GHA workflow (create spec)', async () => {
@@ -1329,10 +1328,12 @@ describe('rdme openapi', () => {
13291328

13301329
describe('command execution in GitHub Actions runner', () => {
13311330
beforeEach(() => {
1332-
beforeGHAEnv();
1331+
githubActionsEnv.before();
13331332
});
13341333

1335-
afterEach(afterGHAEnv);
1334+
afterEach(() => {
1335+
githubActionsEnv.after();
1336+
});
13361337

13371338
it('should error out if multiple possible spec matches were found', () => {
13381339
return expect(run(['--key', key, '--version', version])).rejects.toStrictEqual(
@@ -1432,8 +1433,6 @@ describe('rdme openapi', () => {
14321433
]),
14331434
).resolves.toBe(successfulUpload(spec));
14341435

1435-
after();
1436-
14371436
postMock.done();
14381437
return mock.done();
14391438
});

__tests__/commands/openapi/validate.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import prompts from 'prompts';
44
import { describe, beforeAll, beforeEach, afterEach, it, expect, vi } from 'vitest';
55

66
import Command from '../../../src/commands/openapi/validate.js';
7-
import { after, before } from '../../helpers/get-gha-setup.js';
7+
import { gitMock } from '../../helpers/git-mock.js';
88
import { runCommand, runCommandWithHooks, type OclifOutput } from '../../helpers/oclif.js';
99

1010
describe('rdme openapi validate', () => {
@@ -112,13 +112,13 @@ describe('rdme openapi validate', () => {
112112
let yamlOutput;
113113

114114
beforeEach(() => {
115-
before((fileName, data) => {
115+
gitMock.before((fileName, data) => {
116116
yamlOutput = data;
117117
});
118118
});
119119

120120
afterEach(() => {
121-
after();
121+
gitMock.after();
122122
});
123123

124124
it('should create GHA workflow if user passes in spec via prompts', async () => {

__tests__/helpers/get-gha-setup.ts

-48
This file was deleted.

__tests__/helpers/get-git-mock.ts

-47
This file was deleted.

__tests__/helpers/git-mock.ts

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import type { Response } from 'simple-git';
2+
3+
import fs from 'node:fs';
4+
5+
import { vi } from 'vitest';
6+
7+
import configstore from '../../src/lib/configstore.js';
8+
import * as getPkgVersion from '../../src/lib/getPkg.js';
9+
import { git } from '../../src/lib/git.js';
10+
import * as isCI from '../../src/lib/isCI.js';
11+
12+
const fsWriteFileSync = fs.writeFileSync;
13+
14+
/**
15+
* Creates a mock function for testing `git.remote`.
16+
*/
17+
export function getGitRemoteMock(
18+
/** remote to return (usually `origin`) */
19+
remote = 'origin',
20+
/** git URL for the given remote */
21+
remoteUrl = 'https://github.com/readmeio/rdme.git',
22+
/** the HEAD branch */
23+
defaultBranch = 'main',
24+
) {
25+
return vi.fn<(typeof git)['remote']>(arr => {
26+
// first call (used to grab remote for usage in subsequent commands)
27+
if (!arr.length) {
28+
if (!remote) return Promise.reject(new Error('Bad mock uh oh')) as unknown as Response<string>;
29+
return Promise.resolve(remote) as unknown as Response<string>;
30+
}
31+
// second call (used to grab default branch)
32+
if (arr.length === 2 && arr[0] === 'show' && arr[1] === remote) {
33+
if (remote === 'bad-remote') {
34+
return Promise.reject(
35+
new Error(`fatal: unable to access '${remoteUrl}': Could not resolve host: ${remoteUrl}`),
36+
) as unknown as Response<string>;
37+
}
38+
if (!defaultBranch) return Promise.reject(new Error('Bad mock uh oh')) as unknown as Response<string>;
39+
return Promise.resolve(`* remote origin
40+
Fetch URL: ${remoteUrl}
41+
Push URL: ${remoteUrl}
42+
HEAD branch: ${defaultBranch}
43+
`) as unknown as Response<string>;
44+
}
45+
46+
// third call (used to grab remote URLs)
47+
if (arr.length === 1 && arr[0] === '-v') {
48+
if (!remoteUrl) return Promise.reject(new Error('Bad mock uh oh')) as unknown as Response<string>;
49+
return Promise.resolve(`origin ${remoteUrl} (fetch)
50+
origin ${remoteUrl} (push)
51+
`) as unknown as Response<string>;
52+
}
53+
54+
return Promise.reject(new Error('Bad mock uh oh')) as unknown as Response<string>;
55+
});
56+
}
57+
58+
/**
59+
* Helper functions for setting up and tearing down tests for our GitHub Action onboarding.
60+
*
61+
* @see {@link __tests__/lib/createGHA.test.ts}
62+
*/
63+
export const gitMock = {
64+
before: function before(
65+
/** the mock function that should be called in place of `fs.writeFileSync` */
66+
writeFileSyncCb: typeof fs.writeFileSync = () => {},
67+
) {
68+
fs.writeFileSync = vi.fn(writeFileSyncCb);
69+
70+
git.checkIsRepo = vi.fn(() => {
71+
return Promise.resolve(true) as Response<boolean>;
72+
});
73+
74+
git.remote = getGitRemoteMock();
75+
76+
vi.setSystemTime(new Date('2022'));
77+
78+
vi.stubEnv('TEST_RDME_CREATEGHA', 'true');
79+
80+
const spy = vi.spyOn(getPkgVersion, 'getMajorPkgVersion');
81+
spy.mockResolvedValue(7);
82+
},
83+
84+
after: function after() {
85+
configstore.clear();
86+
fs.writeFileSync = fsWriteFileSync;
87+
vi.clearAllMocks();
88+
vi.unstubAllEnvs();
89+
},
90+
};
91+
92+
/**
93+
* Helper functions for setting up tests for simulating a GitHub Actions runner environment
94+
*/
95+
export const githubActionsEnv = {
96+
before: function before() {
97+
vi.resetModules();
98+
99+
// List of all GitHub Actions env variables:
100+
// https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables
101+
vi.stubEnv('GITHUB_ACTION', '__repo-owner_name-of-action-repo');
102+
vi.stubEnv('GITHUB_ACTIONS', 'true');
103+
vi.stubEnv('GITHUB_REPOSITORY', 'octocat/Hello-World');
104+
vi.stubEnv('GITHUB_RUN_ATTEMPT', '3');
105+
vi.stubEnv('GITHUB_RUN_ID', '1658821493');
106+
vi.stubEnv('GITHUB_RUN_NUMBER', '3');
107+
vi.stubEnv('GITHUB_SERVER_URL', 'https://github.com');
108+
vi.stubEnv('GITHUB_SHA', 'ffac537e6cbbf934b08745a378932722df287a53');
109+
vi.stubEnv('TEST_RDME_CI', 'true');
110+
vi.stubEnv('TEST_RDME_GHA', 'true');
111+
112+
const ciNameSpy = vi.spyOn(isCI, 'ciName');
113+
ciNameSpy.mockReturnValue('GitHub Actions (test)');
114+
},
115+
116+
after: function after() {
117+
vi.unstubAllEnvs();
118+
vi.resetAllMocks();
119+
},
120+
};

__tests__/helpers/oclif.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import path from 'node:path';
55
import { Config } from '@oclif/core';
66
import { captureOutput, runCommand as oclifRunCommand } from '@oclif/test';
77

8+
export type OclifOutput<T = string> = ReturnType<typeof captureOutput<T>>;
9+
810
const testNodeEnv = process.env.NODE_ENV;
911

1012
/**
@@ -35,8 +37,6 @@ export function setupOclifConfig() {
3537
export function runCommand(Command: CommandClass) {
3638
return async function runCommandAgainstArgs(args?: string[]) {
3739
const oclifConfig = await setupOclifConfig();
38-
// @ts-expect-error this is the pattern recommended by the @oclif/test docs.
39-
// Not sure how to get this working with type generics.
4040
return captureOutput<string>(() => Command.run(args, oclifConfig), { testNodeEnv });
4141
};
4242
}

0 commit comments

Comments
 (0)