fix(deploy): resolve Docker startup and CSRF issues
- Rename TASKPLANER_DATA_DIR to DATA_DIR (avoid adapter-node envPrefix conflict) - Add TASKPLANER_ORIGIN for CSRF protection in docker-compose.yml - Add automatic database schema initialization on startup - Add Playwright E2E tests for Docker deployment verification - Update .env.example with correct variable names Fixes container restart loop and 403 errors on form submission. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
109
tests/docker-deployment.spec.ts
Normal file
109
tests/docker-deployment.spec.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
/**
|
||||
* Docker deployment tests
|
||||
*
|
||||
* These tests verify the application works correctly when deployed in Docker.
|
||||
* They specifically test CSRF protection which requires TASKPLANER_ORIGIN to be set.
|
||||
*
|
||||
* Run against Docker: BASE_URL=http://localhost:3000 npx playwright test
|
||||
*/
|
||||
|
||||
test.describe('Docker Deployment', () => {
|
||||
test('application loads', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page).toHaveTitle(/TaskPlaner/i);
|
||||
});
|
||||
|
||||
test('health endpoint returns ok', async ({ request }) => {
|
||||
const response = await request.get('/health');
|
||||
expect(response.status()).toBe(200);
|
||||
expect(await response.text()).toBe('ok');
|
||||
});
|
||||
|
||||
test('can create entry via form submission', async ({ page }) => {
|
||||
// This test verifies CSRF protection is properly configured
|
||||
// If TASKPLANER_ORIGIN is not set, form submissions return 403
|
||||
await page.goto('/');
|
||||
|
||||
// Fill in quick capture form (textarea for content)
|
||||
const contentInput = page.locator('textarea[name="content"]');
|
||||
await contentInput.fill('Test entry from Playwright');
|
||||
|
||||
// Submit the form - button text is "Add"
|
||||
const addButton = page.locator('button[type="submit"]:has-text("Add")');
|
||||
await addButton.click();
|
||||
|
||||
// Wait for the entry to appear in the list
|
||||
await expect(page.locator('text=Test entry from Playwright')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('can toggle entry completion', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Create a task entry first
|
||||
const contentInput = page.locator('textarea[name="content"]');
|
||||
await contentInput.fill('Test task for completion');
|
||||
|
||||
// Select task type
|
||||
const typeSelect = page.locator('select[name="type"]');
|
||||
await typeSelect.selectOption('task');
|
||||
|
||||
const addButton = page.locator('button[type="submit"]:has-text("Add")');
|
||||
await addButton.click();
|
||||
|
||||
// Wait for entry to appear
|
||||
await expect(page.locator('text=Test task for completion')).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Find and click the checkbox to mark complete
|
||||
const checkbox = page.locator('input[type="checkbox"]').first();
|
||||
await checkbox.click();
|
||||
|
||||
// Verify the action completed (no 403 error)
|
||||
// The checkbox should now be checked
|
||||
await expect(checkbox).toBeChecked({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('data persists across container restart', async ({ page }) => {
|
||||
// This test verifies Docker volume persistence
|
||||
const uniqueContent = `Persistence test ${Date.now()}`;
|
||||
|
||||
// Create an entry
|
||||
await page.goto('/');
|
||||
const contentInput = page.locator('textarea[name="content"]');
|
||||
await contentInput.fill(uniqueContent);
|
||||
|
||||
const addButton = page.locator('button[type="submit"]:has-text("Add")');
|
||||
await addButton.click();
|
||||
|
||||
// Wait for entry to appear
|
||||
await expect(page.locator(`text=${uniqueContent}`)).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Restart the container
|
||||
await execAsync('docker compose restart');
|
||||
|
||||
// Wait for container to be healthy again
|
||||
let healthy = false;
|
||||
for (let i = 0; i < 30; i++) {
|
||||
try {
|
||||
const response = await fetch('http://localhost:3000/health');
|
||||
if (response.ok) {
|
||||
healthy = true;
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// Container not ready yet
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
}
|
||||
expect(healthy).toBe(true);
|
||||
|
||||
// Verify entry still exists after restart
|
||||
await page.goto('/');
|
||||
await expect(page.locator(`text=${uniqueContent}`)).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user