Skip to content

Commit

Permalink
repo and install endpoints handle 404
Browse files Browse the repository at this point in the history
  • Loading branch information
jbolda committed Feb 13, 2025
1 parent cfb1095 commit e920b0d
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 17 deletions.
5 changes: 5 additions & 0 deletions .changes/repo-and-installs-404.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@simulacrum/github-api-simulator": patch:enhance
---

All existing custom routes, repository and installations endpoints, now return a 404 in cases where there are no associated resources to match the real API functionality.
27 changes: 17 additions & 10 deletions packages/github-api/src/rest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ const handlers =
_request,
response
) => {
const repos = simulationStore.selectors.allReposWithOrgs(
simulationStore.store.getState()
);
const repos =
simulationStore.selectors.allReposWithOrgs(
simulationStore.store.getState()
) ?? [];
return {
status: 200,
json: {
Expand All @@ -50,39 +51,45 @@ const handlers =
simulationStore.store.getState(),
org
);
if (!install) return response.status(404).send("Not Found");
return { status: 200, json: install };
},
// GET /repos/{owner}/{repo}/installation - Get a repository installation for the authenticated app
"apps/get-repo-installation": async (context, _request, response) => {
const { owner } = context.request.params;
const { owner, repo } = context.request.params;
const install = simulationStore.selectors.getAppInstallation(
simulationStore.store.getState(),
owner
owner,
repo
);
if (!install) return response.status(404).send("Not Found");
return { status: 200, json: install };
},

// GET /orgs/{org}/repos
"repos/list-for-org": async (_context, _request, response) => {
"repos/list-for-org": async (context, _request, response) => {
const { org } = context.request.params;
const repos = simulationStore.selectors.allReposWithOrgs(
simulationStore.store.getState()
simulationStore.store.getState(),
org
);
if (!repos) return response.status(404).send("Not Found");
return { status: 200, json: repos };
},
// L#29067 /repos/{owner}/{repo}/branches
"repos/list-branches": async (_context, _request, response) => {
"repos/list-branches": async (_context, _request, _response) => {
const branches = simulationStore.schema.branches.selectTableAsList(
simulationStore.store.getState()
);
return { status: 200, json: branches };
},
// GET /repos/{owner}/{repo}/commits/{ref}/status
"repos/get-combined-status-for-ref": async (
_context,
context,
request,
response
) => {
const { owner, repo, ref } = request.params;
const { owner, repo, ref } = context.request.params;
const commitStatus = commitStatusResponse({
host: `${request.protocol}://${request.headers.host}`,
owner,
Expand Down
22 changes: 18 additions & 4 deletions packages/github-api/src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,23 @@ const inputSelectors = (args: ExtendSimulationSelectors<ExtendedSchema>) => {
const getAppInstallation = createSelector(
schema.installations.selectTableAsList,
schema.organizations.selectTableAsList,
(_: AnyState, org: string) => org,
(installations, orgs, org) => {
schema.repositories.selectTableAsList,
(_: AnyState, org: string, repo?: string) => ({ org, repo }),
(installations, orgs, repos, { org, repo }) => {
const appInstall = installations.find(
(install) => install.account === org
);
const account = orgs.find((o) => o.login === appInstall?.account);
if (!appInstall) return undefined;
let account = undefined;
if (repo) {
const repoData = repos.find(
(r) => r.owner === appInstall?.account && r.name === repo
);
if (repoData) account = orgs.find((o) => o.login === repoData.owner);
} else {
account = orgs.find((o) => o.login === appInstall?.account);
}
if (!account) return undefined;
return {
...appInstall,
account: { ...account },
Expand All @@ -139,7 +150,10 @@ const inputSelectors = (args: ExtendSimulationSelectors<ExtendedSchema>) => {
const allReposWithOrgs = createSelector(
schema.repositories.selectTableAsList,
schema.organizations.selectTable,
(repos, orgMap) => {
(_: AnyState, org?: string) => org,
(allRepos, orgMap, org) => {
if (org && !orgMap?.[org]) return undefined;
const repos = !org ? allRepos : allRepos.filter((r) => r.owner === org);
return repos.map((repo) => {
const linkedRepo = { ...repo, owner: { ...orgMap[repo.owner] } };
// TODO better option than delete?
Expand Down
27 changes: 26 additions & 1 deletion packages/github-api/tests/installations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ describe.sequential("GET repo endpoints", () => {
})
);
});

it("handles non-existant org", async () => {
let request = await fetch(`${url}/orgs/doesnt-exist/installation`);
expect(request.status).toEqual(404);
});
});

describe("/repos/{owner}/{repo}/installation", () => {
Expand All @@ -53,13 +58,33 @@ describe.sequential("GET repo endpoints", () => {
`${url}/repos/lovely-org/awesome-repo/installation`
);
let response = await request.json();
if (request.status === 502) console.dir(response);
expect(request.status).toEqual(200);
expect(response).toEqual(
expect.objectContaining({
account: expect.objectContaining({ login: "lovely-org" }),
})
);
});

it("handles non-existant org", async () => {
let request = await fetch(
`${url}/repos/an-org/awesome-repo/installation`
);
expect(request.status).toEqual(404);
});

it("handles non-existant repo", async () => {
let request = await fetch(
`${url}/repos/lovely-org/not-awesome-repo/installation`
);
expect(request.status).toEqual(404);
});

it("handles non-existant org and repo", async () => {
let request = await fetch(
`${url}/repos/lovely-but-not/awesome/installation`
);
expect(request.status).toEqual(404);
});
});
});
16 changes: 14 additions & 2 deletions packages/github-api/tests/repositories.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe.sequential("GET repo endpoints", () => {
let app = simulation({
initialState: {
users: [],
organizations: [{ login: "lovely-org" }],
organizations: [{ login: "lovely-org" }, { login: "empty-org" }],
repositories: [{ owner: "lovely-org", name: "awesome-repo" }],
branches: [{ name: "main" }],
blobs: [],
Expand All @@ -25,13 +25,25 @@ describe.sequential("GET repo endpoints", () => {

describe("/orgs/{org}/repos", () => {
it("validates with 200 response", async () => {
let request = await fetch(`${url}/orgs/lovel-org/repos`);
let request = await fetch(`${url}/orgs/lovely-org/repos`);
let response = await request.json();
expect(request.status).toEqual(200);
expect(response).toEqual([
expect.objectContaining({ name: "awesome-repo" }),
]);
});

it("handles org with no repos", async () => {
let request = await fetch(`${url}/orgs/empty-org/repos`);
let response = await request.json();
expect(request.status).toEqual(200);
expect(response).toEqual([]);
});

it("handles non-existant org", async () => {
let request = await fetch(`${url}/orgs/nope-org/repos`);
expect(request.status).toEqual(404);
});
});

describe("/repos/{org}/{repo}/branches", () => {
Expand Down

0 comments on commit e920b0d

Please sign in to comment.