You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ghost/e2e/data-factory/persistence/adapters/api.ts

105 lines
4.4 KiB

import {PersistenceAdapter} from '../adapter';
// HTTP client interface that matches Playwright's API but isn't coupled to it
export interface HttpResponse {
ok(): boolean;
status(): number;
json(): Promise<unknown>;
}
// Generic HTTP client interface that works with any response type that has the required methods
export interface HttpClient<TResponse extends HttpResponse = HttpResponse> {
get(url: string, options?: {headers?: Record<string, string>}): Promise<TResponse>;
post(url: string, options?: {data?: unknown; headers?: Record<string, string>}): Promise<TResponse>;
put(url: string, options?: {data?: unknown; headers?: Record<string, string>}): Promise<TResponse>;
delete(url: string, options?: {headers?: Record<string, string>}): Promise<TResponse>;
}
interface ApiAdapterOptions<TRequest = unknown, TResponse = unknown> {
httpClient: HttpClient;
endpoint: string;
queryParams?: Record<string, string>;
transformRequest?: (data: TRequest) => unknown;
transformResponse?: (response: TResponse) => unknown;
}
/**
* Generic API persistence adapter that works with any HTTP client
* Note: The HTTP client must handle authentication (cookies, tokens, etc.)
* For Playwright tests, the client should preserve the BrowserContext's auth state
*/
export class ApiPersistenceAdapter<TRequest = unknown, TResponse = unknown> implements PersistenceAdapter {
protected httpClient: HttpClient;
protected endpoint: string;
protected queryParams: Record<string, string>;
protected transformRequest: (data: TRequest) => unknown;
protected transformResponse: (response: TResponse) => unknown;
constructor(options: ApiAdapterOptions<TRequest, TResponse>) {
this.httpClient = options.httpClient;
this.endpoint = options.endpoint;
this.queryParams = options.queryParams || {};
this.transformRequest = options.transformRequest || ((data: TRequest) => data as unknown);
this.transformResponse = options.transformResponse || ((response: TResponse) => response as unknown);
}
protected buildUrl(path?: string): string {
const url = path ? `${this.endpoint}/${path}` : this.endpoint;
const params = new URLSearchParams(this.queryParams);
const queryString = params.toString();
return queryString ? `${url}?${queryString}` : url;
}
async insert<T>(entityType: string, data: T): Promise<T> {
const response = await this.httpClient.post(this.buildUrl(), {
data: this.transformRequest(data as unknown as TRequest)
});
if (!response.ok()) {
const errorBody = await response.json().catch(() => null);
const errorMessage = errorBody ? JSON.stringify(errorBody) : '';
throw new Error(`Failed to create ${entityType}: ${response.status()} ${errorMessage}`);
}
const body = await response.json() as TResponse;
return this.transformResponse(body) as T;
}
async findById<T>(entityType: string, id: string): Promise<T> {
const response = await this.httpClient.get(this.buildUrl(id));
if (response.status() === 404) {
throw new Error(`${entityType} with id ${id} not found`);
}
if (!response.ok()) {
throw new Error(`Failed to find ${entityType}: ${response.status()}`);
}
const body = await response.json() as TResponse;
return this.transformResponse(body) as T;
}
async update<T>(entityType: string, id: string, data: Partial<T>): Promise<T> {
const existing = await this.findById<T>(entityType, id);
const response = await this.httpClient.put(this.buildUrl(id), {
data: this.transformRequest({...existing, ...data} as unknown as TRequest)
});
if (!response.ok()) {
throw new Error(`Failed to update ${entityType}: ${response.status()}`);
}
const body = await response.json() as TResponse;
return this.transformResponse(body) as T;
}
async delete(entityType: string, id: string): Promise<void> {
const response = await this.httpClient.delete(this.buildUrl(id));
if (!response.ok() && response.status() !== 404) {
throw new Error(`Failed to delete ${entityType}: ${response.status()}`);
}
}
}