mirror of
https://github.com/icereed/paperless-gpt.git
synced 2025-03-12 21:08:00 -05:00
281 lines
8.9 KiB
TypeScript
281 lines
8.9 KiB
TypeScript
import { Browser, chromium } from '@playwright/test';
|
|
import * as fs from 'fs';
|
|
import { GenericContainer, Network, StartedTestContainer, Wait } from 'testcontainers';
|
|
|
|
export interface TestEnvironment {
|
|
paperlessNgx: StartedTestContainer;
|
|
paperlessGpt: StartedTestContainer;
|
|
browser: Browser;
|
|
cleanup: () => Promise<void>;
|
|
}
|
|
|
|
export const PORTS = {
|
|
paperlessNgx: 8000,
|
|
paperlessGpt: 8080,
|
|
};
|
|
|
|
export async function setupTestEnvironment(): Promise<TestEnvironment> {
|
|
console.log('Setting up test environment...');
|
|
const paperlessPort = PORTS.paperlessNgx;
|
|
const gptPort = PORTS.paperlessGpt;
|
|
|
|
// Create a network for the containers
|
|
const network = await new Network().start();
|
|
|
|
console.log('Starting Redis container...');
|
|
const redis = await new GenericContainer('redis:7')
|
|
.withNetwork(network)
|
|
.withNetworkAliases('redis')
|
|
.start();
|
|
|
|
console.log('Starting Postgres container...');
|
|
const postgres = await new GenericContainer('postgres:15')
|
|
.withNetwork(network)
|
|
.withNetworkAliases('postgres')
|
|
.withEnvironment({
|
|
POSTGRES_DB: 'paperless',
|
|
POSTGRES_USER: 'paperless',
|
|
POSTGRES_PASSWORD: 'paperless'
|
|
})
|
|
.start();
|
|
|
|
console.log('Starting Paperless-ngx container...');
|
|
const paperlessNgx = await new GenericContainer('ghcr.io/paperless-ngx/paperless-ngx:latest')
|
|
.withNetwork(network)
|
|
.withNetworkAliases('paperless-ngx')
|
|
.withEnvironment({
|
|
PAPERLESS_URL: `http://localhost:${paperlessPort}`,
|
|
PAPERLESS_SECRET_KEY: 'change-me',
|
|
PAPERLESS_ADMIN_USER: 'admin',
|
|
PAPERLESS_ADMIN_PASSWORD: 'admin',
|
|
PAPERLESS_TIME_ZONE: 'Europe/Berlin',
|
|
PAPERLESS_OCR_LANGUAGE: 'eng',
|
|
PAPERLESS_REDIS: 'redis://redis:6379',
|
|
PAPERLESS_DBHOST: 'postgres',
|
|
PAPERLESS_DBNAME: 'paperless',
|
|
PAPERLESS_DBUSER: 'paperless',
|
|
PAPERLESS_DBPASS: 'paperless'
|
|
})
|
|
.withExposedPorts(paperlessPort)
|
|
.withWaitStrategy(Wait.forHttp('/api/', paperlessPort))
|
|
.start();
|
|
|
|
const mappedPort = paperlessNgx.getMappedPort(paperlessPort);
|
|
console.log(`Paperless-ngx container started, mapped port: ${mappedPort}`);
|
|
// Create required tag before starting paperless-gpt
|
|
const baseUrl = `http://localhost:${mappedPort}`;
|
|
const credentials = { username: 'admin', password: 'admin' };
|
|
|
|
try {
|
|
console.log('Creating paperless-gpt tag...');
|
|
await createTag(baseUrl, 'paperless-gpt', credentials);
|
|
} catch (error) {
|
|
console.error('Failed to create paperless-gpt tag:', error);
|
|
await paperlessNgx.stop();
|
|
throw error;
|
|
}
|
|
|
|
console.log('Starting Paperless-gpt container...');
|
|
const paperlessGptImage = process.env.PAPERLESS_GPT_IMAGE || 'icereed/paperless-gpt:e2e';
|
|
console.log(`Using image: ${paperlessGptImage}`);
|
|
const paperlessGpt = await new GenericContainer(paperlessGptImage)
|
|
.withNetwork(network)
|
|
.withEnvironment({
|
|
PAPERLESS_BASE_URL: `http://paperless-ngx:${paperlessPort}`,
|
|
PAPERLESS_API_TOKEN: await getApiToken(baseUrl, credentials),
|
|
LLM_PROVIDER: "openai",
|
|
LLM_MODEL: "gpt-4o-mini",
|
|
LLM_LANGUAGE: "english",
|
|
OPENAI_API_KEY: process.env.OPENAI_API_KEY || '',
|
|
})
|
|
.withExposedPorts(gptPort)
|
|
.withWaitStrategy(Wait.forHttp('/', gptPort))
|
|
.start();
|
|
console.log('Paperless-gpt container started');
|
|
|
|
console.log('Launching browser...');
|
|
const browser = await chromium.launch();
|
|
console.log('Browser launched');
|
|
|
|
const cleanup = async () => {
|
|
console.log('Cleaning up test environment...');
|
|
await browser.close();
|
|
await paperlessGpt.stop();
|
|
await paperlessNgx.stop();
|
|
await redis.stop();
|
|
await postgres.stop();
|
|
await network.stop();
|
|
console.log('Test environment cleanup completed');
|
|
};
|
|
|
|
console.log('Test environment setup completed');
|
|
return {
|
|
paperlessNgx,
|
|
paperlessGpt,
|
|
browser,
|
|
cleanup,
|
|
};
|
|
}
|
|
|
|
export async function waitForElement(page: any, selector: string, timeout = 5000): Promise<void> {
|
|
await page.waitForSelector(selector, { timeout });
|
|
}
|
|
|
|
export interface PaperlessDocument {
|
|
id: number;
|
|
title: string;
|
|
content: string;
|
|
tags: number[];
|
|
}
|
|
|
|
// Helper to upload a document via Paperless-ngx API
|
|
export async function uploadDocument(
|
|
baseUrl: string,
|
|
filePath: string,
|
|
title: string,
|
|
credentials: { username: string; password: string }
|
|
): Promise<PaperlessDocument> {
|
|
console.log(`Uploading document: ${title} from ${filePath}`);
|
|
const formData = new FormData();
|
|
const fileData = await fs.promises.readFile(filePath);
|
|
formData.append('document', new Blob([fileData]));
|
|
formData.append('title', title);
|
|
|
|
// Initial upload to get task ID
|
|
const uploadResponse = await fetch(`${baseUrl}/api/documents/post_document/`, {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers: {
|
|
'Authorization': 'Basic ' + btoa(`${credentials.username}:${credentials.password}`),
|
|
},
|
|
});
|
|
|
|
if (!uploadResponse.ok) {
|
|
console.error(`Upload failed with status ${uploadResponse.status}: ${uploadResponse.statusText}`);
|
|
throw new Error(`Failed to upload document: ${uploadResponse.statusText}`);
|
|
}
|
|
|
|
const task_id = await uploadResponse.json();
|
|
|
|
// Poll the tasks endpoint until document is processed
|
|
while (true) {
|
|
console.log(`Checking task status for ID: ${task_id}`);
|
|
const taskResponse = await fetch(`${baseUrl}/api/tasks/?task_id=${task_id}`, {
|
|
headers: {
|
|
'Authorization': 'Basic ' + btoa(`${credentials.username}:${credentials.password}`),
|
|
},
|
|
});
|
|
|
|
if (!taskResponse.ok) {
|
|
throw new Error(`Failed to check task status: ${taskResponse.statusText}`);
|
|
}
|
|
|
|
const taskResultArr = await taskResponse.json();
|
|
console.log(`Task status: ${JSON.stringify(taskResultArr)}`);
|
|
|
|
if (taskResultArr.length === 0) {
|
|
continue;
|
|
}
|
|
const taskResult = taskResultArr[0];
|
|
// Check if task is completed
|
|
if (taskResult.status === 'SUCCESS' && taskResult.id) {
|
|
console.log(`Document processed successfully with ID: ${taskResult.id}`);
|
|
|
|
// Fetch the complete document details
|
|
const documentResponse = await fetch(`${baseUrl}/api/documents/${taskResult.id}/`, {
|
|
headers: {
|
|
'Authorization': 'Basic ' + btoa(`${credentials.username}:${credentials.password}`),
|
|
},
|
|
});
|
|
|
|
if (!documentResponse.ok) {
|
|
throw new Error(`Failed to fetch document details: ${documentResponse.statusText}`);
|
|
}
|
|
|
|
return await documentResponse.json();
|
|
}
|
|
|
|
// Check for failure
|
|
if (taskResult.status === 'FAILED') {
|
|
throw new Error(`Document processing failed: ${taskResult.result}`);
|
|
}
|
|
|
|
// Wait before polling again
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
}
|
|
}
|
|
// Helper to create a tag via Paperless-ngx API
|
|
export async function createTag(
|
|
baseUrl: string,
|
|
name: string,
|
|
credentials: { username: string; password: string }
|
|
): Promise<number> {
|
|
console.log(`Creating tag: ${name}`);
|
|
const response = await fetch(`${baseUrl}/api/tags/`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ name }),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Basic ' + btoa(`${credentials.username}:${credentials.password}`),
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
console.error(`Tag creation failed with status ${response.status}: ${response.statusText}`);
|
|
throw new Error(`Failed to create tag: ${response.statusText}`);
|
|
}
|
|
|
|
const tag = await response.json();
|
|
console.log(`Tag created successfully with ID: ${tag.id}`);
|
|
return tag.id;
|
|
}
|
|
|
|
// Helper to get an API token
|
|
export async function getApiToken(
|
|
baseUrl: string,
|
|
credentials: { username: string; password: string }
|
|
): Promise<string> {
|
|
console.log('Fetching API token');
|
|
const response = await fetch(`${baseUrl}/api/token/`, {
|
|
method: 'POST',
|
|
body: new URLSearchParams({
|
|
username: credentials.username,
|
|
password: credentials.password,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
console.error(`API token fetch failed with status ${response.status}: ${response.statusText}`);
|
|
throw new Error(`Failed to fetch API token: ${response.statusText}`);
|
|
}
|
|
|
|
const token = await response.json();
|
|
console.log(`API token fetched successfully: ${token.token}`);
|
|
return token.token;
|
|
}
|
|
|
|
// Helper to add a tag to a document
|
|
export async function addTagToDocument(
|
|
baseUrl: string,
|
|
documentId: number,
|
|
tagId: number,
|
|
credentials: { username: string; password: string }
|
|
): Promise<void> {
|
|
console.log(`Adding tag ${tagId} to document ${documentId}`);
|
|
const response = await fetch(`${baseUrl}/api/documents/${documentId}/`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify({
|
|
tags: [tagId],
|
|
}),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Basic ' + btoa(`${credentials.username}:${credentials.password}`),
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
console.error(`Tag addition failed with status ${response.status}: ${response.statusText}`);
|
|
throw new Error(`Failed to add tag to document: ${response.statusText}`);
|
|
}
|
|
console.log('Tag added successfully');
|
|
}
|