add github/specToProject
This module builds on the project logic added in #1238, and makes it easy to create projects based on a simple string configuration. Basically, the spec `foo/bar` creates a project containing just the repo foo/bar, and the spec `@foo` creates a project containing all of the repos from the user/organization named foo. This is pulled out of #1233, but I've enhanced it to support organizations out of the box. The method is thoroughly tested.
This commit is contained in:
parent
daa7409abb
commit
0a34c8b036
|
@ -0,0 +1,48 @@
|
|||
// @flow
|
||||
|
||||
import {type Project} from "../../core/project";
|
||||
import {
|
||||
stringToRepoId,
|
||||
githubOwnerPattern,
|
||||
githubRepoPattern,
|
||||
} from "../../core/repoId";
|
||||
import {fetchGithubOrg} from "./fetchGithubOrg";
|
||||
|
||||
/**
|
||||
* Convert a string repository spec into a project.
|
||||
*
|
||||
* The spec may take one of two forms:
|
||||
* - $REPO_OWNER/$REPO_NAME, as in 'sourcecred/example-github'
|
||||
* - @$REPO_OWNER, as in '@sourcecred'
|
||||
*
|
||||
* In either case, we will create a project with the spec as its
|
||||
* id. In the first construction, the project will have a single
|
||||
* RepoId, matching the spec string. In the second construction,
|
||||
* the project will have a RepoId for every repository owned by that
|
||||
* owner.
|
||||
*
|
||||
* A valid GitHub token must be provided, so that it's possible to
|
||||
* enumerate the repos for an org.
|
||||
*/
|
||||
export async function specToProject(
|
||||
spec: string,
|
||||
token: string
|
||||
): Promise<Project> {
|
||||
const repoSpecMatcher = new RegExp(
|
||||
`^${githubOwnerPattern}/${githubRepoPattern}$`
|
||||
);
|
||||
const ownerSpecMatcher = new RegExp(`^@${githubOwnerPattern}$`);
|
||||
if (spec.match(repoSpecMatcher)) {
|
||||
const project: Project = {
|
||||
id: spec,
|
||||
repoIds: [stringToRepoId(spec)],
|
||||
};
|
||||
return project;
|
||||
} else if (spec.match(ownerSpecMatcher)) {
|
||||
const owner = spec.slice(1);
|
||||
const org = await fetchGithubOrg(owner, token);
|
||||
const project: Project = {id: spec, repoIds: org.repos};
|
||||
return project;
|
||||
}
|
||||
throw new Error(`invalid spec: ${spec}`);
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
// @flow
|
||||
|
||||
import {specToProject} from "./specToProject";
|
||||
import {stringToRepoId} from "../../core/repoId";
|
||||
jest.mock("./fetchGithubOrg", () => ({fetchGithubOrg: jest.fn()}));
|
||||
type JestMockFn = $Call<typeof jest.fn>;
|
||||
const fetchGithubOrg: JestMockFn = (require("./fetchGithubOrg")
|
||||
.fetchGithubOrg: any);
|
||||
|
||||
describe("plugins/github/specToProject", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it("works for a repoId", async () => {
|
||||
const spec = "foo/bar";
|
||||
const expected = {
|
||||
id: spec,
|
||||
repoIds: [stringToRepoId(spec)],
|
||||
};
|
||||
const actual = await specToProject(spec, "FAKE_TOKEN");
|
||||
expect(expected).toEqual(actual);
|
||||
expect(fetchGithubOrg).not.toHaveBeenCalled();
|
||||
});
|
||||
it("works for an owner", async () => {
|
||||
const repos = [stringToRepoId("foo/bar"), stringToRepoId("foo/zod")];
|
||||
const spec = "@foo";
|
||||
const token = "FAKE_TOKEN";
|
||||
const fakeOrg = {name: "foo", repos};
|
||||
fetchGithubOrg.mockResolvedValueOnce(fakeOrg);
|
||||
const actual = await specToProject(spec, token);
|
||||
expect(fetchGithubOrg).toHaveBeenCalledWith(fakeOrg.name, token);
|
||||
const expected = {id: spec, repoIds: repos};
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
describe("fails for malformed spec strings", () => {
|
||||
const bad = [
|
||||
"foo",
|
||||
"foo_bar",
|
||||
"@@foo",
|
||||
" @foo ",
|
||||
"foo / bar",
|
||||
"",
|
||||
"@foo/bar",
|
||||
];
|
||||
for (const b of bad) {
|
||||
it(`fails for "${b}"`, () => {
|
||||
expect.assertions(2);
|
||||
const fail = specToProject(b, "FAKE_TOKEN");
|
||||
return (
|
||||
expect(fail)
|
||||
.rejects.toThrow(`invalid spec: ${b}`)
|
||||
// The typedef says toThrow returns void, but this promise chain does
|
||||
// actually work. We don't need help from flow, since tests will fail
|
||||
// if the type is wrong.
|
||||
// $ExpectFlowError
|
||||
.then(() => {
|
||||
expect(fetchGithubOrg).toHaveBeenCalledTimes(0);
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue