Skip to content

Commit

Permalink
test: implement own prismic client
Browse files Browse the repository at this point in the history
  • Loading branch information
angeloashmore committed Dec 19, 2024
1 parent 37228ed commit 4e27e67
Show file tree
Hide file tree
Showing 9 changed files with 14,393 additions and 2,156 deletions.
8,131 changes: 7,137 additions & 994 deletions e2e-projects/app-router/package-lock.json

Large diffs are not rendered by default.

7,325 changes: 6,851 additions & 474 deletions e2e-projects/pages-router/package-lock.json

Large diffs are not rendered by default.

433 changes: 4 additions & 429 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@
"@eslint/js": "^9.16.0",
"@playwright/test": "^1.49.1",
"@prismicio/client": "^7.12.0",
"@prismicio/e2e-tests-utils": "^1.9.1",
"@prismicio/types-internal": "^3.2.0",
"@rollup/plugin-typescript": "^12.1.1",
"@size-limit/preset-small-lib": "^11.1.6",
Expand Down
273 changes: 273 additions & 0 deletions tests/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
import { APIRequestContext, APIResponse } from "playwright/test";
import { CustomType } from "@prismicio/types-internal/lib/customtypes";
import { randomUUID } from "node:crypto";
import assert from "node:assert";

type PrismicURLs = {
wroom: URL;
auth: URL;
customtypes: URL;
};

type RepoURLs = PrismicURLs & {
cdn: URL;
core: URL;
};

type Auth = {
email: string;
password: string;
};

export class Prismic {
urls: PrismicURLs;
#auth: AuthenticatedAPI;

constructor(config: {
baseURL: string;
auth: Auth;
request: APIRequestContext;
}) {
const { baseURL, auth, request } = config;
this.urls = {
wroom: new URL(baseURL),
auth: withSubdomain("auth", baseURL),
customtypes: withSubdomain("customtypes", baseURL),
};
this.#auth = new AuthenticatedAPI({ urls: this.urls, auth, request });
}

async createRepository(args: { defaultLocale: string; prefix?: string }) {
const { defaultLocale, prefix = "e2e-tests" } = args;
const suffix = randomUUID().replace("-", "").slice(0, 16);
const data = {
domain: `${prefix}-${suffix}`,
framework: "vue",
plan: "personal",
isAnnual: "false",
role: "developer",
};

const url = new URL("authentication/newrepository", this.urls.wroom);
const res = await this.#auth.postWroom(url.toString(), { data });
assert(res.ok, `Could not create repository ${data.domain}.`);

const repo = this.getRepo(data.domain);
await repo.setDefaultLocale(defaultLocale);
return repo;
}

getRepo(domain: string) {
return new Repo({ domain, urls: this.urls, auth: this.#auth });
}
}

export class Repo {
readonly domain: string;
urls: RepoURLs;
#auth: AuthenticatedAPI;

constructor(config: {
domain: string;
urls: PrismicURLs;
auth: AuthenticatedAPI;
}) {
const { domain, auth, urls } = config;
this.domain = domain;
this.urls = {
...urls,
wroom: withSubdomain(domain, urls.wroom),
cdn: withSubdomain(domain, withSubdomain("cdn", urls.wroom)),
core: new URL("core/", withSubdomain(domain, urls.wroom)),
};
this.#auth = auth;
}

async setDefaultLocale(locale: string) {
const url = new URL(
`app/settings/multilanguages/${locale}/createMasterLang`,
this.urls.wroom,
);
const res = await this.#auth.postWroom(url.toString());
assert(res.ok, `Could not default locale to ${locale}.`);
}

async createPreview(args: { name: string; url: URL }) {
const { name, url: previewURL } = args;
const url = new URL("previews/new", this.urls.wroom);
const data = {
name,
websiteURL: previewURL.origin,
resolverPath: previewURL.pathname,
};
const res = await this.#auth.postWroom(url.toString(), { data });
assert(res.ok, `Could not create preview ${name}.`);
}

async getPreviewConfigs(): Promise<WroomPreviewConfig[]> {
const url = new URL("repository/preview_configs", this.urls.core);
const res = await this.#auth.get(url.toString());
assert(res.ok, `Could not get preview configs.`);
const json = await res.json();
return json.results;
}

async createPreviewSession(
document: CoreAPIDocument,
): Promise<{ preview_url: string; session_id: string }> {
const configs = await this.getPreviewConfigs();
const config = configs[0];
assert(config, "At least one preview must be configured.");
const url = new URL("previews/session/draft", this.urls.core);
url.searchParams.set("previewId", config.id);
url.searchParams.set("documentId", document.id);
url.searchParams.set("versionId", document.versions[0].version_id);
const res = await this.#auth.get(url.toString());
return await res.json();
}

async addCustomType(customType: CustomType) {
const url = new URL("customtypes/insert", this.urls.customtypes);
const res = await this.#auth.post(url.toString(), {
data: customType,
headers: { repository: this.domain },
});
assert(res.ok, "Could not add custom type.");
}

async createDocument(document: {
title: string;
locale?: string;
group_lang_id?: string;
release_id?: string;
integration_field_ids: string[];
data: Record<string, unknown>;
custom_type_id: string;
tags: string[];
}): Promise<CoreAPIDocument> {
const url = new URL("documents", this.urls.core);
const res = await this.#auth.post(url.toString(), { data: document });
assert(res.status() === 201, "Could not create draft document.");
return await res.json();
}

async publishDocument(id: string) {
const url = new URL(`documents/${id}/draft`, this.urls.core);
const data = { status: "published" };
const res = await this.#auth.patch(url.toString(), { data });
assert(res.status() === 204, `Could not publish document ${id}.`);
}

async createDocumentDraft(
document: CoreAPIDocument,
content: Record<string, unknown>,
): Promise<CoreAPIDocument> {
const { uid, version_id } = document.versions[0];
const url = new URL(`documents/${document.id}/draft`, this.urls.core);
url.searchParams.set("base_version_id", version_id);
const data = {
data: { ...(uid ? { uid } : {}), ...content },
integration_field_ids: [],
tags: [],
};
const res = await this.#auth.put(url.toString(), { data });
return res.json();
}

async getDocumentByUID(type: string, uid: string) {
const url = new URL(`documents`, this.urls.core);
url.searchParams.set("uid", uid);
const res = await this.#auth.get(url.toString());
assert(res.ok, "Could not fetch documents.");
const json: { results: CoreAPIDocument[] } = await res.json();
const doc = json.results.find((result) => result.custom_type_id === type);
assert(doc, `Could not find document with type ${type} and UID ${uid}.`);
return doc;
}
}

class AuthenticatedAPI {
urls: PrismicURLs;
auth: Auth;
#request: APIRequestContext;
#cachedToken?: string;

constructor(config: {
urls: PrismicURLs;
auth: Auth;
request: APIRequestContext;
}) {
this.urls = config.urls;
this.auth = config.auth;
this.#request = config.request;
}

async get(...args: Parameters<APIRequestContext["get"]>) {
const headers = await this.#headers(args[1]?.headers);
return await this.#request.get(args[0], { ...args[1], headers });
}

async post(...args: Parameters<APIRequestContext["post"]>) {
const headers = await this.#headers(args[1]?.headers);
return await this.#request.post(args[0], { ...args[1], headers });
}

async put(...args: Parameters<APIRequestContext["get"]>) {
const headers = await this.#headers(args[1]?.headers);
return await this.#request.put(args[0], { ...args[1], headers });
}

async patch(...args: Parameters<APIRequestContext["get"]>) {
const headers = await this.#headers(args[1]?.headers);
return await this.#request.patch(args[0], { ...args[1], headers });
}

async postWroom(
...args: Parameters<APIRequestContext["post"]>
): Promise<APIResponse> {
const { cookies } = await this.#request.storageState();
const xsrf = cookies.find((cookie) => cookie.name === "X_XSRF")?.value;
if (!xsrf) {
await this.#logInWroom();
return await this.postWroom(...args);
}

console.log({ xsrf });
const url = new URL(args[0]);
url.searchParams.set("_", xsrf);
return await this.#request.post(url.toString(), args[1]);
}

async #logInWroom() {
const url = new URL("/authentication/signin", this.urls.wroom).toString();
const res = await this.#request.post(url, { data: this.auth });
assert(res.ok, "Could not log in to Prismic. Check your credentials.");
}

async #token() {
if (this.#cachedToken) return this.#cachedToken;
const url = new URL("login", this.urls.auth);
const res = await this.#request.post(url.toString(), { data: this.auth });
assert(res.ok, "Authentication failed. No token received.");
return (this.#cachedToken = await res.text());
}

async #headers(existingHeaders?: Record<string, string>) {
const token = await this.#token();
return { authorization: `Bearer ${token}`, ...existingHeaders };
}
}

export type CoreAPIDocument = {
id: string;
custom_type_id: string;
versions: { version_id: string; uid?: string }[];
};

type WroomPreviewConfig = { id: string; label: string; url: string };

function withSubdomain(subdomain: string, url: URL | string) {
const newURL = new URL(url);
newURL.hostname = `${subdomain}.${newURL.hostname}`;
return newURL;
}
11 changes: 3 additions & 8 deletions tests/data/page.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { randomUUID } from "node:crypto";

export const model = {
format: "page",
id: "page",
Expand All @@ -25,14 +23,11 @@ export const model = {
},
} as const;

export function content(
uid: string = randomUUID(),
args: { payload?: string } = {},
) {
const { payload } = args;
export function content(args: { payload?: string; uid?: string } = {}) {
const { payload, uid } = args;

return {
uid,
...(uid ? { uid } : {}),
uid_TYPE: "UID",
payload: payload ?? uid,
payload_TYPE: "Text",
Expand Down
Loading

0 comments on commit 4e27e67

Please sign in to comment.