-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: api tests * fix: types and tests * fix: add common 400 response type * fix: add more common responses --------- Co-authored-by: Eric McDaniel <[email protected]>
- Loading branch information
Showing
9 changed files
with
309 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -371,7 +371,7 @@ const handler = createNextHandler( | |
} | ||
|
||
return { | ||
status: 200, | ||
status: 204, | ||
}; | ||
}, | ||
relations: { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import type { Page } from "@playwright/test"; | ||
|
||
import { expect, test } from "@playwright/test"; | ||
|
||
import { ApiTokenPage } from "../fixtures/api-token-page"; | ||
import { LoginPage } from "../fixtures/login-page"; | ||
import { createCommunity } from "../helpers"; | ||
|
||
const now = new Date().getTime(); | ||
const COMMUNITY_SLUG = `playwright-test-community-${now}`; | ||
|
||
test.describe.configure({ mode: "serial" }); | ||
|
||
let page: Page; | ||
|
||
test.beforeAll(async ({ browser }) => { | ||
page = await browser.newPage(); | ||
const loginPage = new LoginPage(page); | ||
await loginPage.goto(); | ||
await loginPage.loginAndWaitForNavigation(); | ||
|
||
await createCommunity({ | ||
page, | ||
community: { name: `test community ${now}`, slug: COMMUNITY_SLUG }, | ||
}); | ||
|
||
const tokenPage = new ApiTokenPage(page, COMMUNITY_SLUG); | ||
await tokenPage.goto(); | ||
await tokenPage.createToken({ | ||
// expiration: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30), | ||
name: "test token", | ||
permissions: { | ||
community: { read: true, write: true, archive: true }, | ||
pub: { read: true, write: true, archive: true }, | ||
stage: { read: true, write: true, archive: true }, | ||
pubType: { read: true, write: true, archive: true }, | ||
member: { read: true, write: true, archive: true }, | ||
}, | ||
}); | ||
}); | ||
|
||
test("token should exist", async () => { | ||
await expect(page.getByText("test token")).toBeVisible(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import type { Locator, Page } from "@playwright/test"; | ||
import type { z } from "zod"; | ||
|
||
import { expect } from "@playwright/test"; | ||
|
||
import { ApiAccessScope, ApiAccessType } from "db/public"; | ||
|
||
import type { createTokenFormSchema } from "~/app/c/[communitySlug]/settings/tokens/CreateTokenForm"; | ||
|
||
export class ApiTokenPage { | ||
private readonly newTokenNameBox: Locator; | ||
private readonly newTokenDescriptionBox: Locator; | ||
// private readonly newTokenExpiryDatePicker: Locator; | ||
private readonly newTokenCreateButton: Locator; | ||
private readonly communitySlug: string; | ||
|
||
constructor( | ||
public readonly page: Page, | ||
communitySlug: string | ||
) { | ||
this.communitySlug = communitySlug; | ||
this.newTokenNameBox = this.page.getByRole("textbox", { name: "name" }); | ||
this.newTokenDescriptionBox = this.page.getByRole("textbox", { name: "description" }); | ||
// this.newTokenExpiryDatePicker = this.page.getByLabel("Expiry date"); | ||
this.newTokenCreateButton = this.page.getByTestId("create-token-button"); | ||
} | ||
|
||
async goto() { | ||
await this.page.goto(`/c/${this.communitySlug}/settings/tokens`); | ||
} | ||
|
||
async togglePermission(scope: ApiAccessScope, type: ApiAccessType) { | ||
await this.page.getByTestId(`${scope}-${type}-checkbox`).click(); | ||
} | ||
|
||
async createToken( | ||
input: Omit< | ||
z.infer<typeof createTokenFormSchema>, | ||
"permissions" | "issuedById" | "expiration" | ||
> & { | ||
permissions: Partial<z.infer<typeof createTokenFormSchema>["permissions"]> | true; | ||
} | ||
) { | ||
await this.newTokenNameBox.fill(input.name); | ||
await this.newTokenDescriptionBox.fill(input.description ?? ""); | ||
// await this.newTokenExpiryDatePicker.fill(input.expiration.toISOString()); | ||
|
||
for (const scope of Object.values(ApiAccessScope)) { | ||
for (const type of Object.values(ApiAccessType)) { | ||
const value = input.permissions === true ? true : input.permissions[scope]?.[type]; | ||
|
||
if (typeof value === "boolean") { | ||
if (!value) { | ||
continue; | ||
} | ||
await this.togglePermission(scope as ApiAccessScope, type as ApiAccessType); | ||
continue; | ||
} | ||
|
||
if (value && "stage" in value) { | ||
await this.page.getByTestId(`${scope}-${type}-options`).click(); | ||
await this.page.getByTestId(`${scope}-${type}-stages-select`).click(); | ||
for (const stage of value.stages) { | ||
await this.page.getByLabel("Suggestions").getByText(stage).click(); | ||
} | ||
continue; | ||
} | ||
} | ||
} | ||
|
||
await this.newTokenCreateButton.click(); | ||
|
||
const token = await this.page.getByTestId("token-value").textContent(); | ||
|
||
// close modal | ||
await this.page.keyboard.press("Escape"); | ||
return token; | ||
} | ||
|
||
// TODO: delete token | ||
|
||
// TODO: get token permissions | ||
} | ||
|
||
export function expectStatus<T extends { status: number }, S extends T["status"]>( | ||
response: T, | ||
status: S | ||
): asserts response is Extract<T, { status: S }> { | ||
expect(response.status).toBe(status); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import type { APIRequestContext, Page } from "@playwright/test"; | ||
|
||
import { expect, test } from "@playwright/test"; | ||
import { initClient } from "@ts-rest/core"; | ||
|
||
import type { PubsId } from "db/public"; | ||
import { siteApi } from "contracts"; | ||
|
||
import { ApiTokenPage, expectStatus } from "./fixtures/api-token-page"; | ||
import { LoginPage } from "./fixtures/login-page"; | ||
import { createCommunity } from "./helpers"; | ||
|
||
const now = new Date().getTime(); | ||
const COMMUNITY_SLUG = `playwright-test-community-${now}`; | ||
|
||
let page: Page; | ||
|
||
let client: ReturnType<typeof initClient<typeof siteApi, any>>; | ||
|
||
test.beforeAll(async ({ browser }) => { | ||
page = await browser.newPage(); | ||
|
||
const loginPage = new LoginPage(page); | ||
await loginPage.goto(); | ||
await loginPage.loginAndWaitForNavigation(); | ||
|
||
await createCommunity({ | ||
page, | ||
community: { name: `test community ${now}`, slug: COMMUNITY_SLUG }, | ||
}); | ||
|
||
const apiTokenPage = new ApiTokenPage(page, COMMUNITY_SLUG); | ||
await apiTokenPage.goto(); | ||
const token = await apiTokenPage.createToken({ | ||
name: "test token", | ||
description: "test description", | ||
permissions: true, | ||
}); | ||
|
||
client = initClient(siteApi, { | ||
baseUrl: `http://localhost:3000/`, | ||
baseHeaders: { | ||
Authorization: `Bearer ${token}`, | ||
}, | ||
}); | ||
}); | ||
|
||
test.describe("Site API", () => { | ||
test.describe("pubs", () => { | ||
let newPubId: PubsId; | ||
test("should be able to create a pub", async () => { | ||
const pubTypesResponse = await client.pubTypes.getMany({ | ||
params: { | ||
communitySlug: COMMUNITY_SLUG, | ||
}, | ||
}); | ||
|
||
expectStatus(pubTypesResponse, 200); | ||
expect(pubTypesResponse.body).toHaveLength(1); | ||
|
||
const pubType = pubTypesResponse.body[0]; | ||
|
||
const pubResponse = await client.pubs.create({ | ||
headers: { | ||
prefer: "return=representation", | ||
}, | ||
params: { | ||
communitySlug: COMMUNITY_SLUG, | ||
}, | ||
body: { | ||
pubTypeId: pubType.id, | ||
values: { | ||
[`${COMMUNITY_SLUG}:title`]: "Hello world", | ||
}, | ||
}, | ||
}); | ||
|
||
expectStatus(pubResponse, 201); | ||
|
||
expect(pubResponse.body.values).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
fieldSlug: `${COMMUNITY_SLUG}:title`, | ||
value: "Hello world", | ||
}), | ||
]) | ||
); | ||
|
||
newPubId = pubResponse.body.id; | ||
}); | ||
|
||
test("should be able to retrieve a specific pub", async () => { | ||
expect(newPubId).toBeDefined(); | ||
|
||
const response = await client.pubs.get({ | ||
params: { | ||
communitySlug: COMMUNITY_SLUG, | ||
pubId: newPubId, | ||
}, | ||
query: {}, | ||
}); | ||
|
||
expectStatus(response, 200); | ||
expect(response.body.id).toBe(newPubId); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.