224 lines
5.1 KiB
TypeScript
224 lines
5.1 KiB
TypeScript
import { APIRequestContext } from "@playwright/test";
|
|
import * as fs from "fs";
|
|
|
|
/**
|
|
* Frappe API response wrapper.
|
|
*/
|
|
export interface FrappeResponse<T = unknown> {
|
|
message?: T;
|
|
exc?: string;
|
|
exc_type?: string;
|
|
_server_messages?: string;
|
|
}
|
|
|
|
// Path to CSRF token file saved by auth.setup.ts
|
|
const CSRF_FILE = "e2e/.auth/csrf.json";
|
|
|
|
// Cache for CSRF token (read from file once)
|
|
let csrfTokenCache: string | null = null;
|
|
|
|
/**
|
|
* Get CSRF token from the file saved during auth setup.
|
|
* The token is extracted from window.frappe.csrf_token after login.
|
|
*/
|
|
function getCsrfToken(): string {
|
|
// Return cached token if available
|
|
if (csrfTokenCache !== null) {
|
|
return csrfTokenCache;
|
|
}
|
|
|
|
// Read token from file
|
|
try {
|
|
if (fs.existsSync(CSRF_FILE)) {
|
|
const data = JSON.parse(fs.readFileSync(CSRF_FILE, "utf-8"));
|
|
csrfTokenCache = data.csrf_token || "";
|
|
return csrfTokenCache;
|
|
}
|
|
} catch (error) {
|
|
console.warn("Failed to read CSRF token file:", error);
|
|
}
|
|
|
|
csrfTokenCache = "";
|
|
return "";
|
|
}
|
|
|
|
/**
|
|
* Create a new document via Frappe REST API.
|
|
*/
|
|
export async function createDoc<T = Record<string, unknown>>(
|
|
request: APIRequestContext,
|
|
doctype: string,
|
|
doc: Record<string, unknown>,
|
|
): Promise<T> {
|
|
const csrfToken = getCsrfToken();
|
|
|
|
const response = await request.post(`/api/resource/${doctype}`, {
|
|
data: doc,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...(csrfToken ? { "X-Frappe-CSRF-Token": csrfToken } : {}),
|
|
},
|
|
});
|
|
|
|
if (!response.ok()) {
|
|
const error = await response.text();
|
|
throw new Error(`Failed to create ${doctype}: ${error}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
return result.data as T;
|
|
}
|
|
|
|
/**
|
|
* Get a document by name via Frappe REST API.
|
|
*/
|
|
export async function getDoc<T = Record<string, unknown>>(
|
|
request: APIRequestContext,
|
|
doctype: string,
|
|
name: string,
|
|
): Promise<T> {
|
|
const response = await request.get(
|
|
`/api/resource/${doctype}/${encodeURIComponent(name)}`,
|
|
);
|
|
|
|
if (!response.ok()) {
|
|
const error = await response.text();
|
|
throw new Error(`Failed to get ${doctype}/${name}: ${error}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
return result.data as T;
|
|
}
|
|
|
|
/**
|
|
* Update a document via Frappe REST API.
|
|
*/
|
|
export async function updateDoc<T = Record<string, unknown>>(
|
|
request: APIRequestContext,
|
|
doctype: string,
|
|
name: string,
|
|
updates: Record<string, unknown>,
|
|
): Promise<T> {
|
|
const csrfToken = getCsrfToken();
|
|
|
|
const response = await request.put(`/api/resource/${doctype}/${encodeURIComponent(name)}`, {
|
|
data: updates,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...(csrfToken ? { "X-Frappe-CSRF-Token": csrfToken } : {}),
|
|
},
|
|
});
|
|
|
|
if (!response.ok()) {
|
|
const error = await response.text();
|
|
throw new Error(`Failed to update ${doctype}/${name}: ${error}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
return result.data as T;
|
|
}
|
|
|
|
/**
|
|
* Delete a document via Frappe REST API.
|
|
*/
|
|
export async function deleteDoc(
|
|
request: APIRequestContext,
|
|
doctype: string,
|
|
name: string,
|
|
): Promise<void> {
|
|
const csrfToken = getCsrfToken();
|
|
|
|
const response = await request.delete(`/api/resource/${doctype}/${encodeURIComponent(name)}`, {
|
|
headers: {
|
|
...(csrfToken ? { "X-Frappe-CSRF-Token": csrfToken } : {}),
|
|
},
|
|
});
|
|
|
|
if (!response.ok()) {
|
|
const error = await response.text();
|
|
throw new Error(`Failed to delete ${doctype}/${name}: ${error}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Call a Frappe whitelisted method.
|
|
*/
|
|
export async function callMethod<T = unknown>(
|
|
request: APIRequestContext,
|
|
method: string,
|
|
args: Record<string, unknown> = {},
|
|
): Promise<T> {
|
|
const csrfToken = getCsrfToken();
|
|
|
|
const response = await request.post(`/api/method/${method}`, {
|
|
data: args,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...(csrfToken ? { "X-Frappe-CSRF-Token": csrfToken } : {}),
|
|
},
|
|
});
|
|
|
|
if (!response.ok()) {
|
|
const error = await response.text();
|
|
throw new Error(`Failed to call ${method}: ${error}`);
|
|
}
|
|
|
|
const result: FrappeResponse<T> = await response.json();
|
|
return result.message as T;
|
|
}
|
|
|
|
/**
|
|
* Get a list of documents via Frappe REST API.
|
|
*/
|
|
export async function getList<T = Record<string, unknown>>(
|
|
request: APIRequestContext,
|
|
doctype: string,
|
|
options: {
|
|
fields?: string[];
|
|
filters?: Record<string, unknown>;
|
|
limit?: number;
|
|
orderBy?: string;
|
|
} = {},
|
|
): Promise<T[]> {
|
|
const params = new URLSearchParams();
|
|
|
|
if (options.fields) {
|
|
params.set("fields", JSON.stringify(options.fields));
|
|
}
|
|
if (options.filters) {
|
|
params.set("filters", JSON.stringify(options.filters));
|
|
}
|
|
if (options.limit) {
|
|
params.set("limit_page_length", options.limit.toString());
|
|
}
|
|
if (options.orderBy) {
|
|
params.set("order_by", options.orderBy);
|
|
}
|
|
|
|
const response = await request.get(`/api/resource/${doctype}?${params.toString()}`);
|
|
|
|
if (!response.ok()) {
|
|
const error = await response.text();
|
|
throw new Error(`Failed to get list of ${doctype}: ${error}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
return result.data as T[];
|
|
}
|
|
|
|
/**
|
|
* Check if a document exists.
|
|
*/
|
|
export async function docExists(
|
|
request: APIRequestContext,
|
|
doctype: string,
|
|
name: string,
|
|
): Promise<boolean> {
|
|
try {
|
|
await getDoc(request, doctype, name);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|