@prismicio/e2e-tests-utils 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/clients/authenticationApi.cjs +21 -2
  2. package/dist/clients/authenticationApi.cjs.map +1 -1
  3. package/dist/clients/authenticationApi.d.ts +3 -2
  4. package/dist/clients/authenticationApi.js +22 -3
  5. package/dist/clients/authenticationApi.js.map +1 -1
  6. package/dist/clients/contentApi.cjs +141 -0
  7. package/dist/clients/contentApi.cjs.map +1 -0
  8. package/dist/clients/contentApi.d.ts +106 -0
  9. package/dist/clients/contentApi.js +141 -0
  10. package/dist/clients/contentApi.js.map +1 -0
  11. package/dist/clients/coreApi.cjs +12 -2
  12. package/dist/clients/coreApi.cjs.map +1 -1
  13. package/dist/clients/coreApi.d.ts +1 -0
  14. package/dist/clients/coreApi.js +12 -2
  15. package/dist/clients/coreApi.js.map +1 -1
  16. package/dist/clients/customTypesApi.cjs +1 -0
  17. package/dist/clients/customTypesApi.cjs.map +1 -1
  18. package/dist/clients/customTypesApi.js +1 -0
  19. package/dist/clients/customTypesApi.js.map +1 -1
  20. package/dist/clients/manageV2.cjs +145 -0
  21. package/dist/clients/manageV2.cjs.map +1 -0
  22. package/dist/clients/manageV2.d.ts +91 -0
  23. package/dist/clients/manageV2.js +145 -0
  24. package/dist/clients/manageV2.js.map +1 -0
  25. package/dist/clients/migrationApi.cjs +36 -0
  26. package/dist/clients/migrationApi.cjs.map +1 -0
  27. package/dist/clients/migrationApi.d.ts +20 -0
  28. package/dist/clients/migrationApi.js +36 -0
  29. package/dist/clients/migrationApi.js.map +1 -0
  30. package/dist/clients/wroom.cjs +28 -0
  31. package/dist/clients/wroom.cjs.map +1 -1
  32. package/dist/clients/wroom.d.ts +22 -2
  33. package/dist/clients/wroom.js +28 -0
  34. package/dist/clients/wroom.js.map +1 -1
  35. package/dist/index.cjs +4 -0
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.js +4 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/managers/repositories.cjs +25 -18
  41. package/dist/managers/repositories.cjs.map +1 -1
  42. package/dist/managers/repositories.d.ts +12 -22
  43. package/dist/managers/repositories.js +25 -18
  44. package/dist/managers/repositories.js.map +1 -1
  45. package/dist/managers/repository.cjs +184 -41
  46. package/dist/managers/repository.cjs.map +1 -1
  47. package/dist/managers/repository.d.ts +77 -22
  48. package/dist/managers/repository.js +184 -41
  49. package/dist/managers/repository.js.map +1 -1
  50. package/dist/types.d.ts +20 -1
  51. package/dist/utils/cookies.cjs.map +1 -1
  52. package/dist/utils/cookies.js.map +1 -1
  53. package/dist/utils/log.cjs.map +1 -1
  54. package/dist/utils/log.js.map +1 -1
  55. package/package.json +9 -2
  56. package/src/clients/authenticationApi.ts +33 -4
  57. package/src/clients/contentApi.ts +235 -0
  58. package/src/clients/coreApi.ts +14 -1
  59. package/src/clients/manageV2.ts +178 -0
  60. package/src/clients/migrationApi.ts +69 -0
  61. package/src/clients/wroom.ts +42 -2
  62. package/src/index.ts +2 -0
  63. package/src/managers/repositories.ts +46 -37
  64. package/src/managers/repository.ts +263 -70
  65. package/src/types.ts +29 -1
@@ -0,0 +1,235 @@
1
+ import { APIResponse, request } from "@playwright/test";
2
+
3
+ export interface ContentApiError {
4
+ type: string;
5
+ message: string;
6
+ }
7
+ export interface ContentApiSearchResponse {
8
+ page: number;
9
+ results_per_page: number;
10
+ results_size: number;
11
+ total_results_size: number;
12
+ total_pages: number;
13
+ next_page: string | null;
14
+ prev_page: string | null;
15
+ results: ContentApiDocument[];
16
+ }
17
+
18
+ export interface ContentApiDocument {
19
+ id: string;
20
+ uid: string | null;
21
+ url: string | null;
22
+ type: string;
23
+ href: string;
24
+ tags: string[];
25
+ first_publication_date: string;
26
+ last_publication_date: string;
27
+ slugs: string[];
28
+ linked_documents: unknown[];
29
+ lang: string;
30
+ alternate_languages: string[];
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ data: Record<string, any>;
33
+ }
34
+
35
+ export interface ContentApiGraphQLResponse {
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ data: Record<string, any>;
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ errors?: { message: string; locations: any }[];
40
+ }
41
+
42
+ export interface ContentApiRef {
43
+ id: string;
44
+ ref: string;
45
+ label: string;
46
+ isMasterRef?: boolean;
47
+ }
48
+
49
+ interface ContentApiRepoConfigResponse {
50
+ refs: ContentApiRef[];
51
+ bookmarks: Record<string, unknown>;
52
+ types: Record<string, string>;
53
+ languages: {
54
+ id: string;
55
+ name: string;
56
+ is_master: boolean;
57
+ }[];
58
+ oauth_initiate: string;
59
+ oauth_token: string;
60
+ tags: string[];
61
+ license: string;
62
+ version: string;
63
+ }
64
+
65
+ type SearchQueryParams = {
66
+ ref?: string;
67
+ query?: string;
68
+ q?: string;
69
+ graphQuery?: string;
70
+ fetchLinks?: string;
71
+ orderings?: string;
72
+ pageSize?: number;
73
+ page?: number;
74
+ lang?: string;
75
+ after?: string;
76
+ routes?: string;
77
+ };
78
+
79
+ type ApiVersions = "v1" | "v2";
80
+
81
+ /**
82
+ * Client to query the Prismic content api. Uses Playwright to benefit from
83
+ * network request traces in your reports. The @prismicio/client package could
84
+ * have been used but had too many abstractions (caching, error handling) that
85
+ * we didn't want for test execution. See api docs:
86
+ * https://prismic.io/docs/rest-api-technical-reference
87
+ */
88
+ export class ContentApiClient {
89
+ #version: ApiVersions;
90
+ #accessToken: string | undefined;
91
+
92
+ /**
93
+ * @example To instantiate the class:
94
+ *
95
+ * ```js
96
+ * new ContentApiClient("https://my-repo.cdn.prismic.io", {
97
+ * version: "v2",
98
+ * accessToken: "my-secret-token",
99
+ * });
100
+ * ```
101
+ *
102
+ * @param baseURL - the api base URL like https://my-repo.cdn.prismic.io
103
+ * @param config - Optional configuration
104
+ *
105
+ * - {@link config.version}: Api version, default is "v2"
106
+ * - {@link config.accessToken}: Access token for the content api -
107
+ * https://prismic.io/docs/access-token
108
+ */
109
+ constructor(
110
+ private readonly baseURL: string,
111
+ config: { version?: ApiVersions; accessToken?: string } = {},
112
+ ) {
113
+ this.#version = config.version || "v2";
114
+ this.#accessToken = config.accessToken;
115
+ }
116
+
117
+ #context() {
118
+ return request.newContext({
119
+ baseURL: this.baseURL,
120
+ // reset any Authorization header set globally with Playwright
121
+ // didn't find a way to delete it
122
+ extraHTTPHeaders: { Authorization: "" },
123
+ });
124
+ }
125
+
126
+ async get(
127
+ path: string,
128
+ params?: SearchQueryParams,
129
+ headers?: Record<string, string>,
130
+ ): Promise<APIResponse> {
131
+ const context = await this.#context();
132
+ const securityParams = {
133
+ ...(this.#accessToken ? { access_token: this.#accessToken } : {}),
134
+ };
135
+
136
+ return context.get(path, {
137
+ params: { ...securityParams, ...params },
138
+ headers,
139
+ });
140
+ }
141
+
142
+ async getAsJson<T>(
143
+ url: string,
144
+ params?: SearchQueryParams,
145
+ headers?: Record<string, string>,
146
+ ): Promise<T> {
147
+ const response = await this.get(url, params, headers);
148
+
149
+ return response.json() as T;
150
+ }
151
+
152
+ /** Query the graphql api - https://prismic.io/docs/graphql-technical-reference */
153
+ async graphql(
154
+ ref: string,
155
+ query: string,
156
+ ): Promise<ContentApiGraphQLResponse> {
157
+ return this.getAsJson<ContentApiGraphQLResponse>(
158
+ "/graphql",
159
+ { query },
160
+ { "Prismic-ref": ref },
161
+ );
162
+ }
163
+
164
+ async getRefs(): Promise<ContentApiRef[]> {
165
+ const data = await this.getAsJson<ContentApiRepoConfigResponse>(
166
+ `/api/${this.#version}`,
167
+ );
168
+
169
+ return data.refs;
170
+ }
171
+
172
+ async getMasterRef(): Promise<string> {
173
+ const refs = await this.getRefs();
174
+
175
+ const masterRef = refs.find(({ isMasterRef }) => isMasterRef === true);
176
+ if (!masterRef) {
177
+ throw "No master ref found";
178
+ }
179
+
180
+ return masterRef.ref;
181
+ }
182
+
183
+ async getRefByReleaseID(releaseID: string): Promise<string> {
184
+ const refs = await this.getRefs();
185
+ const release = refs.find(({ id }) => id === releaseID);
186
+ if (!release) {
187
+ throw `No ref found for release ${releaseID}`;
188
+ }
189
+
190
+ return release.ref;
191
+ }
192
+
193
+ /** Search documents from the `/api/v<version>/documents/search` endpoint. */
194
+ async search(filters?: SearchQueryParams): Promise<ContentApiSearchResponse> {
195
+ let ref = filters?.ref;
196
+ if (!ref) {
197
+ ref = await this.getMasterRef();
198
+ }
199
+
200
+ return this.getAsJson<ContentApiSearchResponse>(
201
+ `/api/${this.#version}/documents/search`,
202
+ {
203
+ ...filters,
204
+ ref,
205
+ },
206
+ );
207
+ }
208
+
209
+ /**
210
+ * Search for a single document by its it, using the
211
+ * `/api/v<version>/documents/search` endpoint and a default filter.
212
+ */
213
+ async searchByID(
214
+ id: string,
215
+ filters?: SearchQueryParams,
216
+ ): Promise<ContentApiSearchResponse> {
217
+ return this.search({ ...filters, q: `[[at(document.id, "${id}")]]` });
218
+ }
219
+
220
+ /** Retrieve a single document or undefined if not found. */
221
+ async getDocumentByID(
222
+ id: string,
223
+ filters?: SearchQueryParams,
224
+ ): Promise<ContentApiDocument | undefined> {
225
+ const data = await this.searchByID(id, filters);
226
+ switch (data.results_size) {
227
+ case 0:
228
+ return;
229
+ case 1:
230
+ return data.results[0];
231
+ default:
232
+ throw new Error(`Too many documents returned with the same id ${id}`);
233
+ }
234
+ }
235
+ }
@@ -12,6 +12,7 @@ type Language = {
12
12
 
13
13
  export type CoreClient = {
14
14
  getLanguages(): Promise<Language[]>;
15
+ publishDraft(documentId: string): Promise<void>;
15
16
  };
16
17
 
17
18
  /** Client for interacting with the core API */
@@ -41,7 +42,7 @@ export const createCoreApiClient = (
41
42
  "core/repository",
42
43
  );
43
44
 
44
- if (![200].includes(result.status) || !result.data.languages) {
45
+ if (200 !== result.status || !result.data.languages) {
45
46
  logHttpResponse(result);
46
47
  throw new Error("Could not get languages from the core api.");
47
48
  }
@@ -51,7 +52,19 @@ export const createCoreApiClient = (
51
52
  return result.data.languages;
52
53
  }
53
54
 
55
+ async function publishDraft(documentId: string): Promise<void> {
56
+ const result = await client.patch(`core/documents/${documentId}/draft`, {
57
+ status: "published",
58
+ });
59
+
60
+ if (204 !== result.status) {
61
+ logHttpResponse(result);
62
+ throw new Error(`Could not publish document with id ${documentId}`);
63
+ }
64
+ }
65
+
54
66
  return {
55
67
  getLanguages,
68
+ publishDraft,
56
69
  };
57
70
  };
@@ -0,0 +1,178 @@
1
+ import axios, { AxiosInstance, AxiosResponse } from "axios";
2
+ import jwt from "jsonwebtoken";
3
+
4
+ import { ManageV2Config } from "../types";
5
+
6
+ import { extractCookie } from "../utils/cookies";
7
+
8
+ const ManageV2StaticAuthor = "prismic-e2e-tests-utils";
9
+
10
+ /**
11
+ * Client for interacting with ManageV2 routes to perform operations on
12
+ * repositories.
13
+ */
14
+ export class ManageV2Client {
15
+ readonly client: AxiosInstance; // Not private to have it available on tests
16
+ private token: string | null = null;
17
+
18
+ /**
19
+ * @param baseURL - The base URL of the Wroom app. ex: https://prismic.io
20
+ * @param config - ManageV2 configuration variable to call ManageV2.
21
+ */
22
+ constructor(baseUrl: string, config?: ManageV2Config | undefined) {
23
+ this.token = config ? this.generateToken(config) : null;
24
+
25
+ this.client = axios.create({
26
+ baseURL: `${baseUrl}/manageroutes/`,
27
+ withCredentials: true,
28
+ validateStatus: () => true,
29
+ headers: {
30
+ "User-Agent": "prismic-cli/prismic-e2e-tests-utils",
31
+ Authorization: `Bearer ${this.token}`,
32
+ },
33
+ });
34
+
35
+ // cookies are not forwarded automatically in a non-browser env
36
+ this.client.interceptors.response.use((response) => {
37
+ const cookies = response.headers["set-cookie"];
38
+ if (cookies && extractCookie(cookies, "SESSION")) {
39
+ this.client.defaults.headers["Cookie"] = cookies;
40
+ }
41
+
42
+ return response;
43
+ });
44
+ }
45
+
46
+ /**
47
+ * The function generates a JWT token using the provided configuration
48
+ * parameters.
49
+ *
50
+ * @param {ManageV2Config} config - The `config` parameter in the
51
+ * `generateToken` function is of type `ManageV2Config`. It contains the
52
+ * following properties:
53
+ *
54
+ * @returns A JSON Web Token (JWT) is being returned by the `generateToken`
55
+ * function. The token is signed using the provided `config.secret` with the
56
+ * HS256 algorithm and includes the specified audience and issuer values.
57
+ */
58
+ private generateToken(config: ManageV2Config): string {
59
+ return jwt.sign({}, config.secret, {
60
+ algorithm: "HS256",
61
+ audience: config.audience,
62
+ issuer: "prismic.io",
63
+ });
64
+ }
65
+
66
+ /**
67
+ * The function `assertTokenExist` checks if a token exists before executing a
68
+ * given function.
69
+ *
70
+ * @param f - The `assertTokenExist` function takes a function `f` as a
71
+ * parameter. This function `f` should accept a parameter of type
72
+ * `Parameters` and return a Promise that resolves to an AxiosResponse. The
73
+ * `assertTokenExist` function ensures that a token is not null before
74
+ * calling the provided
75
+ *
76
+ * @returns A function is being returned that takes a parameter of type
77
+ * Parameters and checks if the token is null. If the token is null, an
78
+ * error is thrown. Otherwise, the function f is called with the provided
79
+ * parameters.
80
+ */
81
+ private assertTokenExist<Parameters>(
82
+ f: (_: Parameters) => Promise<AxiosResponse>,
83
+ ) {
84
+ return (params: Parameters) => {
85
+ if (this.token === null) {
86
+ throw new Error(
87
+ "Undefined configuration for ManageV2 while trying to use it's routes",
88
+ );
89
+ } else {
90
+ return f(params);
91
+ }
92
+ };
93
+ }
94
+
95
+ getBaseURL(): string {
96
+ return this.client.getUri();
97
+ }
98
+
99
+ /**
100
+ * The function `toggleRolePerLocal` enable or disable the role per local
101
+ * feature
102
+ *
103
+ * @param repository - The Repository name
104
+ * @param enabled - The feature new status
105
+ */
106
+ toggleRolePerLocal = this.assertTokenExist(
107
+ (params: { repository: string; enabled: boolean }) => {
108
+ return this.client.post("/repository/toggleRolesPerLocale", {
109
+ domain: params.repository,
110
+ enabled: params.enabled,
111
+ author: ManageV2StaticAuthor,
112
+ });
113
+ },
114
+ );
115
+
116
+ /**
117
+ * The function `changePlan` changes the plan of a repository the database
118
+ *
119
+ * @param repository - The Repository name
120
+ * @param newPlanId - The new planId
121
+ * @param bypassManualBilling - Beware that using this will not verify that
122
+ * the database and Stripe are in sync
123
+ */
124
+ changePlan = this.assertTokenExist(
125
+ ({
126
+ repository,
127
+ newPlanId,
128
+ bypassManualBilling = false,
129
+ }: {
130
+ repository: string;
131
+ newPlanId: string;
132
+ bypassManualBilling?: boolean;
133
+ }) => {
134
+ return this.client.post(
135
+ `/repository/changePlan?bypassManualBilling=${bypassManualBilling}`,
136
+ {
137
+ domain: repository,
138
+ newPlan: newPlanId,
139
+ author: ManageV2StaticAuthor,
140
+ },
141
+ );
142
+ },
143
+ );
144
+
145
+ /**
146
+ * The function `addUserToRepository` adds a user corresponding to the email
147
+ * to the given repository
148
+ *
149
+ * @param repository - The Repository name
150
+ * @param email - The user email
151
+ */
152
+ addUserToRepository = this.assertTokenExist(
153
+ ({ repository, email }: { repository: string; email: string }) => {
154
+ return this.client.post("/repository/addUser", {
155
+ domain: repository,
156
+ email: email,
157
+ author: ManageV2StaticAuthor,
158
+ });
159
+ },
160
+ );
161
+
162
+ /**
163
+ * The function `removeUserFromRepository` removes a user corresponding to the
164
+ * email from the given repository
165
+ *
166
+ * @param repository - The Repository name
167
+ * @param email - The user email
168
+ */
169
+ removeUserFromRepository = this.assertTokenExist(
170
+ ({ repository, email }: { repository: string; email: string }) => {
171
+ return this.client.post("/repository/removeUser", {
172
+ domain: repository,
173
+ email: email,
174
+ author: ManageV2StaticAuthor,
175
+ });
176
+ },
177
+ );
178
+ }
@@ -0,0 +1,69 @@
1
+ import axios, { AxiosInstance } from "axios";
2
+
3
+ import { logHttpResponse } from "../utils/log";
4
+
5
+ import { AuthenticationClient } from "./authenticationApi";
6
+
7
+ export type MigrationClient = {
8
+ createDocument(data: DocumentPayload): Promise<DocumentResponse>;
9
+ };
10
+
11
+ export type DocumentPayload = {
12
+ uid?: string | null;
13
+ lang: string;
14
+ title: string;
15
+ tags?: string[];
16
+ alternate_language_id?: string;
17
+ data: Record<string, unknown>;
18
+ };
19
+
20
+ export type DocumentResponse = {
21
+ id: string;
22
+ type: string;
23
+ lang: string;
24
+ title: string;
25
+ uid?: string;
26
+ };
27
+
28
+ export const createMigrationApiClient = (
29
+ baseURL: string,
30
+ repository: string,
31
+ authClient: AuthenticationClient,
32
+ ): MigrationClient => {
33
+ const client: AxiosInstance = axios.create({
34
+ baseURL,
35
+ headers: {
36
+ repository,
37
+ },
38
+ validateStatus: () => true, // Don't throw on 4XX errors
39
+ });
40
+
41
+ // Add an interceptor to authenticate requests
42
+ client.interceptors.request.use(async (config) => {
43
+ const auth = "Authorization";
44
+ if (!config.headers[auth]) {
45
+ const token = await authClient.getToken();
46
+ config.headers[auth] = `Bearer ${token}`;
47
+ }
48
+ config.headers["repository"] = repository;
49
+
50
+ return config;
51
+ });
52
+
53
+ async function createDocument(
54
+ data: DocumentPayload,
55
+ ): Promise<DocumentResponse> {
56
+ const result = await client.post<DocumentResponse>("/documents", data);
57
+
58
+ if (201 !== result.status) {
59
+ logHttpResponse(result);
60
+ throw new Error("Could not create a document with the migration api.");
61
+ }
62
+
63
+ return result.data;
64
+ }
65
+
66
+ return {
67
+ createDocument,
68
+ };
69
+ };
@@ -1,6 +1,6 @@
1
1
  import axios, { AxiosInstance, AxiosResponse } from "axios";
2
2
 
3
- import { Credentials } from "../types";
3
+ import { AuthConfig } from "../types";
4
4
 
5
5
  import { extractCookie } from "../utils/cookies";
6
6
  import { logHttpResponse, logger } from "../utils/log";
@@ -20,7 +20,7 @@ export class WroomClient {
20
20
  */
21
21
  constructor(
22
22
  baseURL: string,
23
- private readonly auth: Credentials,
23
+ private readonly auth: AuthConfig,
24
24
  ) {
25
25
  this.client = axios.create({
26
26
  baseURL,
@@ -84,6 +84,46 @@ export class WroomClient {
84
84
  return response;
85
85
  }
86
86
 
87
+ /**
88
+ * Update the role of a user in the repository
89
+ *
90
+ * @param {string} repository - The repository name
91
+ * @param {string} email - The email of the user
92
+ * @param {string} role - The new role to be given
93
+ *
94
+ * @returns The Axios Reponse
95
+ */
96
+ async updateRole(
97
+ repository: string,
98
+ email: string,
99
+ role: string,
100
+ ): Promise<AxiosResponse> {
101
+ const params = new URLSearchParams({ email, profile: role });
102
+ const path = `/app/settings/users/profiles?${params.toString()}`;
103
+
104
+ return this.post(repository, path);
105
+ }
106
+
107
+ /**
108
+ * Update the role of a user in the repository
109
+ *
110
+ * @param {string} repository - The repository name
111
+ * @param {string} email - The email of the user
112
+ * @param {string} rolePerLocal - The role per local object
113
+ *
114
+ * @returns The Axios Reponse
115
+ */
116
+ async updateRolePerLocal(
117
+ repository: string,
118
+ email: string,
119
+ rolePerLocal: Record<string, string>,
120
+ ): Promise<AxiosResponse> {
121
+ const params = new URLSearchParams({ email });
122
+ const path = `/app/settings/users/rolesperlocale?${params.toString()}`;
123
+
124
+ return this.post(repository, path, rolePerLocal);
125
+ }
126
+
87
127
  /**
88
128
  * Authenticate Wroom to call Wroom unofficial endpoints.
89
129
  *
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from "./managers/repositories";
2
2
  export * from "./managers/repository";
3
+ export * from "./clients/contentApi";
4
+ export * from "./clients/migrationApi";
3
5
  export * from "./types";