mirror of
https://github.com/ramvignesh-b/pi-ku.git
synced 2026-05-04 08:56:52 +00:00
ci: add sll support and enhance e2e workflow
This commit is contained in:
+21
-19
@@ -1,25 +1,27 @@
|
||||
# Pi Ku E2E Environment Configuration Template
|
||||
# DATABASE
|
||||
DB_NAME=piku_test_db
|
||||
DB_USER=test
|
||||
DB_PASSWORD=password123
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5433
|
||||
|
||||
# Database (Postgres)
|
||||
E2E_DB_NAME=piku_e2e_db
|
||||
E2E_DB_PORT=5433
|
||||
E2E_DB_USER=piku_test
|
||||
E2E_DB_PASS=piku_test
|
||||
E2E_DB_DB=piku_e2e
|
||||
# SSL
|
||||
SSL_ENABLED=false
|
||||
|
||||
# Backend (Django)
|
||||
E2E_BACKEND_PORT=8001
|
||||
SECRET_KEY=e2e-secret-key-for-piku-testing
|
||||
# DJANGO
|
||||
DEBUG=True
|
||||
ALLOWED_HOSTS=*
|
||||
CORS_ALLOWED_ORIGINS=https://localhost:5173
|
||||
FRONTEND_URL=https://localhost:5173
|
||||
EMAIL_HOST=localhost
|
||||
EMAIL_PORT=1025
|
||||
SECRET_KEY=django-insecure-initial-key
|
||||
BACKEND_DOMAIN=127.0.0.1
|
||||
BACKEND_PORT=8001
|
||||
|
||||
# EMAIL
|
||||
EMAIL_HOST=127.0.0.1
|
||||
EMAIL_PORT=1026
|
||||
FROM_EMAIL="Test <test@pi-ku.app>"
|
||||
EMAIL_HOST_USER=
|
||||
EMAIL_HOST_PASSWORD=
|
||||
FROM_EMAIL=testing@piku.local
|
||||
MAILPIT_API_URL=http://localhost:8025/api/v1
|
||||
EMAIL_API_PORT=8026
|
||||
|
||||
# Frontend (Vite/Playwright)
|
||||
VITE_API_URL=https://localhost:8001
|
||||
# FRONTEND
|
||||
FRONTEND_PORT=5199
|
||||
FRONTEND_DOMAIN=127.0.0.1
|
||||
|
||||
+8
-9
@@ -5,21 +5,20 @@ DB_PASSWORD=password123
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
|
||||
# SSL
|
||||
SSL_ENABLED=true
|
||||
|
||||
# DJANGO
|
||||
DEBUG=True
|
||||
SECRET_KEY=django-secret-key
|
||||
ALLOWED_HOSTS=localhost,127.0.0.1
|
||||
CORS_ALLOWED_ORIGINS=https://localhost:5173,https://127.0.0.1:5173
|
||||
BACKEND_DOMAIN=127.0.0.1
|
||||
BACKEND_PORT=8000
|
||||
|
||||
# EMAIL
|
||||
EMAIL_HOST=localhost
|
||||
EMAIL_HOST=127.0.0.1
|
||||
EMAIL_PORT=1025
|
||||
FROM_EMAIL=Pi Ku <no-reply@piku.app>
|
||||
EMAIL_HOST_USER=root
|
||||
EMAIL_HOST_PASSWORD=password123
|
||||
FROM_EMAIL=Pi Ku <no-reply@test.com>
|
||||
|
||||
# FRONTEND
|
||||
VITE_API_URL=https://localhost:8000
|
||||
FRONTEND_PORT=5173
|
||||
FRONTEND_URL=https://localhost:5173
|
||||
FRONTEND_DOMAIN=localhost
|
||||
FRONTEND_DOMAIN=127.0.0.1
|
||||
|
||||
@@ -101,21 +101,6 @@ jobs:
|
||||
name: E2E Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup-environment
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
POSTGRES_DB: piku_e2e
|
||||
POSTGRES_USER: piku_test
|
||||
POSTGRES_PASSWORD: piku_test
|
||||
ports:
|
||||
- 5433:5432
|
||||
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||
mailpit:
|
||||
image: axllent/mailpit:latest
|
||||
ports:
|
||||
- 8025:8025
|
||||
- 1025:1025
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
@@ -12,3 +12,4 @@ dist/
|
||||
|
||||
# Certificates
|
||||
certs/*.pem
|
||||
tmp/
|
||||
|
||||
+13
-11
@@ -19,12 +19,17 @@ import environ
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
# Load environment variables
|
||||
# Load dotenv files
|
||||
env = environ.Env()
|
||||
# Allow overriding the .env file path (useful for E2E testing/CI)
|
||||
env_file = os.environ.get("PIKU_ENV_FILE", os.path.join(BASE_DIR.parent, ".env"))
|
||||
env_file = os.path.join(BASE_DIR.parent, ".env")
|
||||
if os.path.exists(env_file):
|
||||
environ.Env.read_env(env_file)
|
||||
environ.Env.read_env(env_file, overwrite=False)
|
||||
|
||||
|
||||
SSL_ENABLED = env("SSL_ENABLED") == "true"
|
||||
FRONTEND_URL = f"https://{env('FRONTEND_DOMAIN')}" if SSL_ENABLED else f"http://{env('FRONTEND_DOMAIN')}"
|
||||
if env("FRONTEND_PORT"):
|
||||
FRONTEND_URL += f":{env('FRONTEND_PORT')}"
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
|
||||
@@ -35,7 +40,7 @@ SECRET_KEY = env("SECRET_KEY")
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = env("DEBUG")
|
||||
|
||||
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS") or []
|
||||
ALLOWED_HOSTS = [env("FRONTEND_DOMAIN")]
|
||||
|
||||
|
||||
# Application definition
|
||||
@@ -50,6 +55,7 @@ INSTALLED_APPS = [
|
||||
"corsheaders",
|
||||
"users",
|
||||
"letters",
|
||||
"scripts",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
@@ -82,7 +88,7 @@ DATABASES = {
|
||||
}
|
||||
}
|
||||
|
||||
CORS_ALLOWED_ORIGINS = env.list("CORS_ALLOWED_ORIGINS")
|
||||
CORS_ALLOWED_ORIGINS = [FRONTEND_URL]
|
||||
CORS_ALLOW_CREDENTIALS = True
|
||||
|
||||
AUTH_USER_MODEL = "users.User"
|
||||
@@ -107,7 +113,7 @@ NOTE: COOKIE_SAMESITE: Lax is used to allow cross-site redirection, like links
|
||||
AUTH_COOKIE = {
|
||||
"NAME": "refresh_token",
|
||||
"DOMAIN": None,
|
||||
"SECURE": True,
|
||||
"SECURE": SSL_ENABLED,
|
||||
"HTTPONLY": True,
|
||||
"SAMESITE": "Lax",
|
||||
}
|
||||
@@ -117,12 +123,8 @@ EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
||||
EMAIL_HOST = env("EMAIL_HOST")
|
||||
EMAIL_PORT = env("EMAIL_PORT")
|
||||
EMAIL_USE_TLS = not DEBUG
|
||||
EMAIL_HOST_USER = env("EMAIL_HOST_USER")
|
||||
EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD")
|
||||
FROM_EMAIL = env("FROM_EMAIL")
|
||||
|
||||
FRONTEND_URL = env("FRONTEND_URL")
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **options):
|
||||
"""
|
||||
Check if SSL is enabled in the environment variables.
|
||||
If SSL is enabled, use runserver_plus command.
|
||||
If SSL is not enabled, use runserver command.
|
||||
"""
|
||||
ssl_enabled = os.getenv("SSL_ENABLED", "false").lower() == "true"
|
||||
domain = os.getenv("BACKEND_DOMAIN", "127.0.0.1")
|
||||
port = os.getenv("BACKEND_PORT", "8000")
|
||||
addrport = f"{domain}:{port}"
|
||||
|
||||
if ssl_enabled:
|
||||
self.stdout.write(self.style.SUCCESS(f"Starting with SSL on {addrport}..."))
|
||||
call_command(
|
||||
"runserver_plus",
|
||||
addrport,
|
||||
cert_file=settings.BASE_DIR / "../certs/localhost.pem",
|
||||
key_file=settings.BASE_DIR / "../certs/localhost-key.pem",
|
||||
)
|
||||
else:
|
||||
self.stdout.write(self.style.WARNING(f"Starting without SSL on {addrport}..."))
|
||||
call_command("runserver", addrport)
|
||||
@@ -1,3 +1,5 @@
|
||||
from unittest.mock import _patch_dict
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.urls import reverse
|
||||
@@ -19,9 +21,10 @@ class AuthTests(APITestCase):
|
||||
self.refresh_url = reverse("token_refresh")
|
||||
self.logout_url = reverse("logout")
|
||||
|
||||
@_patch_dict("config.settings.AUTH_COOKIE", {"SECURE": True})
|
||||
def test_login_sets_secure_cookie(self):
|
||||
"""
|
||||
Tests if the Login API can generate access token and set secure cookie for refresh token.
|
||||
Tests if the Login API can generate access token and set secure cookie (when ssl is enabled) for refresh token.
|
||||
"""
|
||||
data = {"email": self.user.email, "password": self.password}
|
||||
cookie_name = "refresh_token"
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@
|
||||
"noUnusedVariables": "error"
|
||||
}
|
||||
},
|
||||
"includes": ["**", "!backend"]
|
||||
"includes": ["**/src", "!backend"]
|
||||
},
|
||||
"assist": {
|
||||
"actions": {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
services:
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
container_name: ${DB_NAME}
|
||||
environment:
|
||||
POSTGRES_DB: ${DB_NAME}
|
||||
POSTGRES_USER: ${DB_USER}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
ports:
|
||||
- "${DB_PORT}:5432"
|
||||
restart: never
|
||||
|
||||
mailpit:
|
||||
image: axllent/mailpit
|
||||
container_name: piku_test_mail
|
||||
ports:
|
||||
- "${EMAIL_API_PORT}:8025"
|
||||
- "${EMAIL_PORT}:1025"
|
||||
restart: never
|
||||
@@ -34,6 +34,7 @@ export async function registerAndLogin(
|
||||
// 2. Activation via Mailpit
|
||||
logger.info(`[Auth] Polling Mailpit for activation email...`);
|
||||
const activationLink = await MailpitHelper.getActivationLink(email);
|
||||
|
||||
await page.goto(activationLink);
|
||||
|
||||
await expect(page.getByText(/account activated/i)).toBeVisible();
|
||||
|
||||
@@ -7,7 +7,7 @@ export interface MailpitMessage {
|
||||
To: { Address: string }[];
|
||||
}
|
||||
|
||||
const MAILPIT_API_URL = process.env.MAILPIT_API_URL;
|
||||
const MAILPIT_API_URL = `http://${process.env.EMAIL_HOST}:${process.env.EMAIL_API_PORT}/api/v1`;
|
||||
|
||||
export const MailpitHelper = {
|
||||
getActivationLink: async (
|
||||
@@ -18,7 +18,6 @@ export const MailpitHelper = {
|
||||
const requestContext = await request.newContext();
|
||||
|
||||
while (Date.now() - startTime < timeout) {
|
||||
// Search specifically for the recipient to reduce data transfer
|
||||
const response = await requestContext.get(`${MAILPIT_API_URL}/search`, {
|
||||
params: { query: `to:${email}`, limit: 1 },
|
||||
});
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import process, { env } from "node:process";
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
import dotenv from "dotenv";
|
||||
import { getBaseUrl } from "./utils/url-builder";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
*/
|
||||
dotenv.config({ path: path.resolve(process.cwd(), "../.env.e2e") });
|
||||
const baseUrl = getBaseUrl(
|
||||
env.SSL_ENABLED === "true",
|
||||
env.FRONTEND_DOMAIN,
|
||||
env.FRONTEND_PORT,
|
||||
);
|
||||
|
||||
console.log(baseUrl);
|
||||
export default defineConfig({
|
||||
timeout: 60000,
|
||||
expect: {
|
||||
@@ -26,7 +34,7 @@ export default defineConfig({
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: process.env.FRONTEND_URL,
|
||||
baseURL: baseUrl,
|
||||
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
||||
actionTimeout: 20000,
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
@@ -53,8 +61,12 @@ export default defineConfig({
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: "bun run dev",
|
||||
url: process.env.FRONTEND_URL,
|
||||
command: "bun run dev -- --mode e2e",
|
||||
url: getBaseUrl(
|
||||
process.env.SSL_ENABLED === "true",
|
||||
process.env.FRONTEND_DOMAIN,
|
||||
process.env.FRONTEND_PORT,
|
||||
),
|
||||
reuseExistingServer: !process.env.CI,
|
||||
ignoreHTTPSErrors: true,
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@ import { server } from "../../test/mocks/server";
|
||||
import { useAuthStore } from "../store/useAuthStore";
|
||||
import { api } from "./apiClient";
|
||||
|
||||
const API_URL = "http://piku-server";
|
||||
const VITE_API_URL = "http://piku-server";
|
||||
|
||||
beforeEach(() => {
|
||||
useAuthStore.setState({
|
||||
@@ -24,7 +24,7 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
vi.stubEnv("API_URL", API_URL);
|
||||
vi.stubEnv("VITE_API_URL", VITE_API_URL);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
@@ -37,7 +37,7 @@ describe("request interceptor", () => {
|
||||
|
||||
let capturedAuthHeader = "";
|
||||
server.use(
|
||||
http.get(`${API_URL}/api/auth/me/`, ({ request }) => {
|
||||
http.get(`${VITE_API_URL}/api/auth/me/`, ({ request }) => {
|
||||
capturedAuthHeader = request.headers.get("Authorization") ?? "";
|
||||
return HttpResponse.json(mockUser);
|
||||
}),
|
||||
@@ -51,7 +51,7 @@ describe("request interceptor", () => {
|
||||
it("should not send Authorization header when the store has no token", async () => {
|
||||
let capturedAuthHeader: string | null = "";
|
||||
server.use(
|
||||
http.get(`${API_URL}/api/auth/me/`, ({ request }) => {
|
||||
http.get(`${VITE_API_URL}/api/auth/me/`, ({ request }) => {
|
||||
capturedAuthHeader = request.headers.get("Authorization");
|
||||
return HttpResponse.json({});
|
||||
}),
|
||||
@@ -70,14 +70,14 @@ describe("response interceptor", () => {
|
||||
let _refreshApiCallCount = 0;
|
||||
|
||||
server.use(
|
||||
http.get(`${API_URL}/api/auth/me/`, ({ request }) => {
|
||||
http.get(`${VITE_API_URL}/api/auth/me/`, ({ request }) => {
|
||||
meApiCallCount++;
|
||||
if (request.headers.get("Authorization") === "Bearer expired-token") {
|
||||
return new HttpResponse(null, { status: 401 });
|
||||
}
|
||||
return HttpResponse.json(mockUser);
|
||||
}),
|
||||
http.post(`${API_URL}/api/auth/refresh/`, () => {
|
||||
http.post(`${VITE_API_URL}/api/auth/refresh/`, () => {
|
||||
_refreshApiCallCount++;
|
||||
return HttpResponse.json({ access: "refreshed-token" });
|
||||
}),
|
||||
@@ -94,13 +94,13 @@ describe("response interceptor", () => {
|
||||
useAuthStore.getState().setAuth("expired-token", mockUser);
|
||||
|
||||
server.use(
|
||||
http.get(`${API_URL}/api/auth/me/`, ({ request }) => {
|
||||
http.get(`${VITE_API_URL}/api/auth/me/`, ({ request }) => {
|
||||
if (request.headers.get("Authorization") === "Bearer expired-token") {
|
||||
return new HttpResponse(null, { status: 401 });
|
||||
}
|
||||
return HttpResponse.json(mockUser);
|
||||
}),
|
||||
http.post(`${API_URL}/api/auth/refresh/`, () =>
|
||||
http.post(`${VITE_API_URL}/api/auth/refresh/`, () =>
|
||||
HttpResponse.json({ access: "refreshed-token" }),
|
||||
),
|
||||
);
|
||||
@@ -115,14 +115,14 @@ describe("response interceptor", () => {
|
||||
|
||||
server.use(
|
||||
http.get(
|
||||
`${API_URL}/api/auth/me/`,
|
||||
`${VITE_API_URL}/api/auth/me/`,
|
||||
() =>
|
||||
new HttpResponse(JSON.stringify({ detail: "Invalid token" }), {
|
||||
status: 401,
|
||||
}),
|
||||
),
|
||||
http.post(
|
||||
`${API_URL}/api/auth/refresh/`,
|
||||
`${VITE_API_URL}/api/auth/refresh/`,
|
||||
() =>
|
||||
new HttpResponse(JSON.stringify({ detail: "Refresh failed" }), {
|
||||
status: 401,
|
||||
|
||||
@@ -17,7 +17,7 @@ import { useAuth } from "./useAuth";
|
||||
vi.mock("../utils/crypto");
|
||||
vi.mock("../utils/keystore");
|
||||
|
||||
const API_URL = "http://piku-server";
|
||||
const VITE_API_URL = "http://piku-server";
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -112,7 +112,7 @@ describe("logout", () => {
|
||||
it("should call the logout API endpoint", async () => {
|
||||
let logoutCalled = false;
|
||||
server.use(
|
||||
http.post(`${API_URL}/api/auth/logout/`, () => {
|
||||
http.post(`${VITE_API_URL}/api/auth/logout/`, () => {
|
||||
logoutCalled = true;
|
||||
return HttpResponse.json({});
|
||||
}),
|
||||
@@ -139,7 +139,7 @@ describe("logout", () => {
|
||||
it("should clear the auth store even if the API call fails", async () => {
|
||||
server.use(
|
||||
http.post(
|
||||
`${API_URL}/api/auth/logout/`,
|
||||
`${VITE_API_URL}/api/auth/logout/`,
|
||||
() => new HttpResponse(null, { status: 500 }),
|
||||
),
|
||||
);
|
||||
@@ -163,7 +163,7 @@ describe("initialize", () => {
|
||||
});
|
||||
let refreshCalled = false;
|
||||
server.use(
|
||||
http.post(`${API_URL}/api/auth/refresh/`, () => {
|
||||
http.post(`${VITE_API_URL}/api/auth/refresh/`, () => {
|
||||
refreshCalled = true;
|
||||
return HttpResponse.json({ access: "new-token" });
|
||||
}),
|
||||
@@ -194,7 +194,7 @@ describe("initialize", () => {
|
||||
it("should preserve the master key even if the refresh attempt fails", async () => {
|
||||
server.use(
|
||||
http.post(
|
||||
`${API_URL}/api/auth/refresh/`,
|
||||
`${VITE_API_URL}/api/auth/refresh/`,
|
||||
() => new HttpResponse(null, { status: 401 }),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
export const getBaseUrl = (
|
||||
isSslEnabled: boolean,
|
||||
domain: string | undefined,
|
||||
port: string | undefined,
|
||||
): string => {
|
||||
const uriScheme = isSslEnabled ? "https" : "http";
|
||||
const baseURL = `${uriScheme}://${domain}${port ? `:${port}` : ""}`;
|
||||
return baseURL;
|
||||
};
|
||||
+25
-10
@@ -3,24 +3,39 @@ import path from "node:path";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig, loadEnv } from "vite";
|
||||
import { getBaseUrl } from "./utils/url-builder";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, "../", "");
|
||||
return {
|
||||
envDir: "../",
|
||||
plugins: [react(), tailwindcss()],
|
||||
server: {
|
||||
port: Number(env.FRONTEND_PORT),
|
||||
host: env.FRONTEND_DOMAIN,
|
||||
https: {
|
||||
const isSslEnabled = env.SSL_ENABLED === "true";
|
||||
let ssl_certs: { key: Buffer; cert: Buffer };
|
||||
|
||||
if (isSslEnabled) {
|
||||
ssl_certs = {
|
||||
key: fs.readFileSync(
|
||||
path.resolve(__dirname, "../certs/localhost-key.pem"),
|
||||
),
|
||||
cert: fs.readFileSync(
|
||||
path.resolve(__dirname, "../certs/localhost.pem"),
|
||||
),
|
||||
cert: fs.readFileSync(path.resolve(__dirname, "../certs/localhost.pem")),
|
||||
};
|
||||
}
|
||||
|
||||
const baseApiUrl = getBaseUrl(
|
||||
isSslEnabled,
|
||||
env.BACKEND_DOMAIN,
|
||||
env.BACKEND_PORT,
|
||||
);
|
||||
console.log(baseApiUrl);
|
||||
return {
|
||||
envDir: "../",
|
||||
plugins: [react(), tailwindcss()],
|
||||
define: {
|
||||
"import.meta.env.VITE_API_URL": JSON.stringify(baseApiUrl),
|
||||
},
|
||||
server: {
|
||||
port: Number(env.FRONTEND_PORT),
|
||||
host: env.FRONTEND_DOMAIN,
|
||||
https: isSslEnabled ? ssl_certs : undefined,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
[31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* Running on https://127.0.0.1:8001
|
||||
[33mPress CTRL+C to quit[0m
|
||||
* Restarting with stat
|
||||
* Debugger is active!
|
||||
* Debugger PIN: 411-535-418
|
||||
Unauthorized: /api/auth/refresh/
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:15] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||
Unauthorized: /api/auth/refresh/
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:15] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||
Unauthorized: /api/auth/refresh/
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:15] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||
Unauthorized: /api/auth/refresh/
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:16] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:16] "OPTIONS /api/auth/register/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:16] "OPTIONS /api/auth/register/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:16] "OPTIONS /api/auth/register/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:16] "OPTIONS /api/auth/register/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:17] "[35m[1mPOST /api/auth/register/ HTTP/1.1[0m" 201 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:17] "[35m[1mPOST /api/auth/register/ HTTP/1.1[0m" 201 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:17] "[35m[1mPOST /api/auth/register/ HTTP/1.1[0m" 201 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:17] "[35m[1mPOST /api/auth/register/ HTTP/1.1[0m" 201 -
|
||||
Unauthorized: /api/auth/refresh/
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:19] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:19] "GET /api/auth/activate/YmFlZTYyNTgtOTcxMi00ZjFmLWE1YTgtYzBiOGMwODdkY2Zi/d755fh-d81b8ac647be32c14f996ab09e783392/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:20] "OPTIONS /api/auth/login/ HTTP/1.1" 200 -
|
||||
Unauthorized: /api/auth/refresh/
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:20] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:20] "GET /api/auth/activate/YzJjM2NkOWUtZmIxYS00MGI2LWFiM2EtYTQwYmI5MDJjZGY2/d755fh-a2be30e77657af68697d0b7be375e5ab/ HTTP/1.1" 200 -
|
||||
Unauthorized: /api/auth/refresh/
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:20] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:20] "GET /api/auth/activate/Yzc1MDFhMTctY2EzOC00YTY0LTkyNmYtNWYyZjgzNDUyZGQ1/d755fh-677d8f6662cce0e74bb9a776663617a9/ HTTP/1.1" 200 -
|
||||
/var/home/atom/Documents/code/pi ku/backend/.venv/lib64/python3.14/site-packages/jwt/api_jwt.py:147: InsecureKeyLengthWarning: The HMAC key is 27 bytes long, which is below the minimum recommended length of 32 bytes for SHA256. See RFC 7518 Section 3.2.
|
||||
return self._jws.encode(
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:20] "POST /api/auth/login/ HTTP/1.1" 200 -
|
||||
Unauthorized: /api/auth/refresh/
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:20] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:20] "OPTIONS /api/auth/me/ HTTP/1.1" 200 -
|
||||
/var/home/atom/Documents/code/pi ku/backend/.venv/lib64/python3.14/site-packages/jwt/api_jwt.py:365: InsecureKeyLengthWarning: The HMAC key is 27 bytes long, which is below the minimum recommended length of 32 bytes for SHA256. See RFC 7518 Section 3.2.
|
||||
decoded = self.decode_complete(
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:21] "OPTIONS /api/auth/login/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:21] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:21] "GET /api/auth/activate/YjI4MjczYWMtYWNlNC00OGRlLWJmZDItZmMyMTJiZjM2MDZl/d755fh-7728ee068008d7513a64dbe6282954f3/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:21] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:21] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:21] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:21] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:21] "OPTIONS /api/auth/login/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:21] "POST /api/auth/login/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:21] "OPTIONS /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:22] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:22] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:22] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:22] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:22] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:22] "OPTIONS /api/auth/login/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:22] "OPTIONS /api/letters/77cbf618-f4e2-4d33-87c2-60e5c93c9711/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:22] "[35m[1mPUT /api/letters/77cbf618-f4e2-4d33-87c2-60e5c93c9711/ HTTP/1.1[0m" 201 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:22] "POST /api/auth/login/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:22] "OPTIONS /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:22] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:22] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:22] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:22] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:22] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:23] "POST /api/auth/login/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:23] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:23] "OPTIONS /api/letters/e8f47036-6e57-41f5-b057-8ea712589a73/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:23] "OPTIONS /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:23] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:23] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:23] "[35m[1mPUT /api/letters/e8f47036-6e57-41f5-b057-8ea712589a73/ HTTP/1.1[0m" 201 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:23] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:23] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:23] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:23] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:24] "GET /api/letters/77cbf618-f4e2-4d33-87c2-60e5c93c9711/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:24] "GET /api/letters/77cbf618-f4e2-4d33-87c2-60e5c93c9711/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:24] "OPTIONS /api/letters/4e2af91a-1651-4bcd-85b5-c469ee4a73e3/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:24] "[35m[1mPUT /api/letters/4e2af91a-1651-4bcd-85b5-c469ee4a73e3/ HTTP/1.1[0m" 201 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:26] "POST /api/auth/refresh/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:26] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:26] "GET /api/letters/77cbf618-f4e2-4d33-87c2-60e5c93c9711/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:26] "POST /api/auth/refresh/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:26] "GET /api/letters/77cbf618-f4e2-4d33-87c2-60e5c93c9711/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:26] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:26] "GET /api/letters/77cbf618-f4e2-4d33-87c2-60e5c93c9711/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:26] "GET /api/letters/4e2af91a-1651-4bcd-85b5-c469ee4a73e3/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:26] "GET /api/letters/77cbf618-f4e2-4d33-87c2-60e5c93c9711/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:26] "GET /api/letters/4e2af91a-1651-4bcd-85b5-c469ee4a73e3/ HTTP/1.1" 200 -
|
||||
Unauthorized: /api/auth/refresh/
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:30] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||
Unauthorized: /api/auth/refresh/
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:30] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:31] "OPTIONS /api/auth/register/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:32] "OPTIONS /api/auth/register/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:32] "[35m[1mPOST /api/auth/register/ HTTP/1.1[0m" 201 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:32] "[35m[1mPOST /api/auth/register/ HTTP/1.1[0m" 201 -
|
||||
Unauthorized: /api/auth/refresh/
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:34] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:35] "OPTIONS /api/auth/register/ HTTP/1.1" 200 -
|
||||
Unauthorized: /api/auth/refresh/
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:35] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:36] "[35m[1mPOST /api/auth/register/ HTTP/1.1[0m" 201 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:36] "OPTIONS /api/auth/register/ HTTP/1.1" 200 -
|
||||
Unauthorized: /api/auth/refresh/
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:36] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:36] "GET /api/auth/activate/M2M0YzFiNTItMjE5Mi00Y2VmLWIwZmItMDlkNDg5NWE4NWU0/d755fw-9c2c43d45732c1cd5ccbef20d3dc4181/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:37] "OPTIONS /api/auth/login/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:37] "[35m[1mPOST /api/auth/register/ HTTP/1.1[0m" 201 -
|
||||
Unauthorized: /api/auth/refresh/
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:37] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:37] "GET /api/auth/activate/MjdiM2E1M2ItOTZlYS00Y2Y1LWFhMmQtZThjY2ZkNGQ5ZjQ1/d755fw-e60e3d0b66162c75962beea97a40b4c7/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:38] "POST /api/auth/login/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:38] "OPTIONS /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:38] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:38] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:38] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:38] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:38] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:38] "OPTIONS /api/auth/login/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:39] "POST /api/auth/login/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:39] "OPTIONS /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:39] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:39] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:39] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:39] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:39] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:40] "OPTIONS /api/letters/351f0e2c-e10f-4851-9836-bc387758dbad/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:40] "[35m[1mPUT /api/letters/351f0e2c-e10f-4851-9836-bc387758dbad/ HTTP/1.1[0m" 201 -
|
||||
Unauthorized: /api/auth/refresh/
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:40] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:40] "GET /api/auth/activate/MDkwNTg5MDctNjAzOS00NDgwLTlkYTktNmUxOWE5ZTBjODJh/d755g0-546e5f4ee3ed111de64f011ac8d02836/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:41] "OPTIONS /api/auth/login/ HTTP/1.1" 200 -
|
||||
Unauthorized: /api/auth/refresh/
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:41] "[31m[1mPOST /api/auth/refresh/ HTTP/1.1[0m" 401 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:41] "GET /api/auth/activate/MzAxODRjMWEtMTYxZC00ZTczLThlMWMtM2FmY2RmZDg2NThk/d755g1-2ae1dec8cbac57d223c0cd206a8741a8/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:42] "POST /api/auth/login/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:42] "OPTIONS /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:42] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:42] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:42] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:42] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:42] "OPTIONS /api/auth/login/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:42] "OPTIONS /api/letters/5dec1a15-1d19-47de-b5a5-21bc1845f90a/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:42] "[35m[1mPUT /api/letters/5dec1a15-1d19-47de-b5a5-21bc1845f90a/ HTTP/1.1[0m" 201 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:43] "POST /api/auth/login/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:43] "OPTIONS /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:43] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:43] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:43] "OPTIONS /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:43] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:43] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:43] "POST /api/auth/refresh/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:43] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:43] "GET /api/letters/351f0e2c-e10f-4851-9836-bc387758dbad/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:43] "GET /api/letters/351f0e2c-e10f-4851-9836-bc387758dbad/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:46] "POST /api/auth/refresh/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:46] "POST /api/auth/refresh/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:46] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:46] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:46] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:46] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:46] "GET /api/letters/351f0e2c-e10f-4851-9836-bc387758dbad/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:46] "GET /api/letters/351f0e2c-e10f-4851-9836-bc387758dbad/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:47] "OPTIONS /api/letters/e95e2a4c-811a-45b1-9bbd-dafdb180a88f/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:47] "[35m[1mPUT /api/letters/e95e2a4c-811a-45b1-9bbd-dafdb180a88f/ HTTP/1.1[0m" 201 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:47] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:47] "GET /api/letters/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:49] "GET /api/letters/e95e2a4c-811a-45b1-9bbd-dafdb180a88f/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:49] "GET /api/letters/e95e2a4c-811a-45b1-9bbd-dafdb180a88f/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:51] "POST /api/auth/refresh/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:51] "GET /api/auth/me/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:51] "GET /api/letters/e95e2a4c-811a-45b1-9bbd-dafdb180a88f/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:51] "GET /api/letters/e95e2a4c-811a-45b1-9bbd-dafdb180a88f/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:51] "GET /api/letters/e95e2a4c-811a-45b1-9bbd-dafdb180a88f/ HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [16/Apr/2026 18:45:51] "GET /api/letters/e95e2a4c-811a-45b1-9bbd-dafdb180a88f/ HTTP/1.1" 200 -
|
||||
Starting with SSL on 127.0.0.1:8001...
|
||||
/usr/lib64/python3.14/multiprocessing/resource_tracker.py:396: UserWarning: resource_tracker: There appear to be 1 leaked semaphore objects to clean up at shutdown: {'/mp-uf_ylwyc'}
|
||||
warnings.warn(
|
||||
+25
-76
@@ -1,98 +1,47 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Get absolute path of project root
|
||||
PROJECT_ROOT=$(pwd)
|
||||
# Use podman if available. Not everyone has it
|
||||
CONTAINER_BIN=$(command -v podman || command -v docker)
|
||||
|
||||
# Configuration
|
||||
ENV_FILE="$PROJECT_ROOT/.env.e2e"
|
||||
ENV_FILE="./.env.e2e"
|
||||
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
echo "[INFO] Loading configuration from $ENV_FILE..."
|
||||
echo "Loading settings..."
|
||||
set -a
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
elif [ "$CI" != "true" ]; then
|
||||
echo "[ERROR] $ENV_FILE not found! Please create it for local testing (use .env.e2e.example as template)."
|
||||
exit 1
|
||||
else
|
||||
echo "[INFO] Running in CI mode (using direct environment variables)..."
|
||||
echo "Error: Configuration file $ENV_FILE is missing!!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Map E2E variables to Django expected names
|
||||
# In CI, these should be set via GitHub Actions env variables
|
||||
export DB_NAME=${E2E_DB_DB:-piku_e2e}
|
||||
export DB_USER=${E2E_DB_USER:-piku_test}
|
||||
export DB_PASSWORD=${E2E_DB_PASS:-piku_test}
|
||||
export DB_HOST=${E2E_DB_HOST:-localhost}
|
||||
export DB_PORT=${E2E_DB_PORT:-5433}
|
||||
export E2E_BACKEND_PORT=${E2E_BACKEND_PORT:-8001}
|
||||
|
||||
echo "[START] Initializing E2E Test Environment..."
|
||||
|
||||
# 1. Cleanup / Start Services (Skip in CI)
|
||||
if [ "$CI" != "true" ]; then
|
||||
if podman ps -a --format "{{.Names}}" | grep -q "^$E2E_DB_NAME$"; then
|
||||
echo "[CLEANUP] Removing existing container $E2E_DB_NAME..."
|
||||
podman rm -f $E2E_DB_NAME
|
||||
fi
|
||||
|
||||
echo "[DB] Starting disposable Postgres on port $DB_PORT..."
|
||||
podman run --name $E2E_DB_NAME \
|
||||
-e POSTGRES_DB=$DB_NAME \
|
||||
-e POSTGRES_USER=$DB_USER \
|
||||
-e POSTGRES_PASSWORD=$DB_PASSWORD \
|
||||
-p $DB_PORT:5432 \
|
||||
-d docker.io/library/postgres:16-alpine > /dev/null
|
||||
|
||||
echo "[DB] Waiting for Postgres to be ready..."
|
||||
until podman exec $E2E_DB_NAME pg_isready -U $DB_USER > /dev/null 2>&1; do
|
||||
sleep 1
|
||||
done
|
||||
echo "[DB] Postgres is ready."
|
||||
fi
|
||||
|
||||
# Trap to ensure cleanup
|
||||
# This cleans up containers. Very useful for local e2e to free system resources immediately.
|
||||
cleanup() {
|
||||
echo "[CLEANUP] Stopping services..."
|
||||
if [ "$CI" != "true" ]; then
|
||||
podman rm -f $E2E_DB_NAME || true
|
||||
fi
|
||||
if [ ! -z "$BACKEND_PID" ]; then
|
||||
kill "$BACKEND_PID" 2>/dev/null || true
|
||||
fi
|
||||
echo "Cleaning up..."
|
||||
$CONTAINER_BIN rm -f "$DB_NAME" 2>/dev/null || true
|
||||
[ -n "$BACKEND_PID" ] && kill "$BACKEND_PID" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# 2. Prepare Backend
|
||||
echo "[BACKEND] Running database migrations..."
|
||||
export PIKU_ENV_FILE="$ENV_FILE"
|
||||
(cd backend && uv run manage.py migrate --noinput)
|
||||
echo "Starting Database..."
|
||||
$CONTAINER_BIN compose -f "./docker-compose.e2e.yml" up -d
|
||||
|
||||
echo "[BACKEND] Starting server on port $E2E_BACKEND_PORT..."
|
||||
(cd backend && uv run manage.py runserver_plus --cert-file ../certs/localhost.pem --key-file ../certs/localhost-key.pem $E2E_BACKEND_PORT) > /tmp/piku_e2e_backend.log 2>&1 &
|
||||
# postgress will take some time, so we wait, and no race condition. Also, no point in logging this output
|
||||
until $CONTAINER_BIN exec "$DB_NAME" pg_isready -U "${DB_USER:-test}" >/dev/null 2>&1; do
|
||||
echo "Waiting for DB..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "Starting Backend..."
|
||||
mkdir -p ./tmp/logs
|
||||
(cd backend && uv run manage.py migrate)
|
||||
(cd backend && uv run manage.py serve) > ./tmp/logs/backend.log 2>&1 &
|
||||
BACKEND_PID=$!
|
||||
|
||||
echo "[BACKEND] Waiting for server to respond..."
|
||||
until curl -sk https://localhost:$E2E_BACKEND_PORT > /dev/null; do
|
||||
sleep 1
|
||||
if ! kill -0 $BACKEND_PID 2>/dev/null; then
|
||||
echo "[ERROR] Backend failed to start. Logs:"
|
||||
cat /tmp/piku_e2e_backend.log
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo "[BACKEND] Server is ready."
|
||||
|
||||
# 3. Run Playwright
|
||||
export VITE_API_URL="https://localhost:$E2E_BACKEND_PORT"
|
||||
|
||||
if [ "$CI" = "true" ]; then
|
||||
echo "[TEST] Running Playwright Tests (CI)..."
|
||||
(cd frontend && bun run test:e2e "$@")
|
||||
cd frontend && bun run test:e2e "$@"
|
||||
else
|
||||
echo "[TEST] Running Playwright Tests in Distrobox..."
|
||||
(cd frontend && distrobox-enter --name ubuntu-24.04 -- env VITE_API_URL=$VITE_API_URL bun run test:e2e "$@")
|
||||
# Because playwright decided not to support Fedora :)
|
||||
cd frontend && distrobox-enter --name ubuntu-24.04 -- bun run test:e2e "$@"
|
||||
fi
|
||||
|
||||
echo "[SUCCESS] E2E Tests Completed."
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
(podman compose up -d) &
|
||||
(cd backend && uv run manage.py runserver) &
|
||||
(cd backend && uv run manage.py serve) &
|
||||
(cd frontend && bun run dev)
|
||||
|
||||
Reference in New Issue
Block a user