docs+deploy: production status, lessons learned (9 bugs), deploy compose, e2e test tools
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# Tests
|
||||
|
||||
E2E-Testdoppel ohne echtes AYON-Addon (Phase-1-Verifikation, Stand 04.09.2026).
|
||||
|
||||
## Ablauf auf orange
|
||||
|
||||
```bash
|
||||
# 1. Mock-Addon als Container (simuliert /issue + /exchange des AYON-Addons)
|
||||
docker run -d --name mock-exchange --network refboard-ayon_default \
|
||||
-e REFBOARD_API_KEY=<key aus .env> \
|
||||
-v $PWD/tests/mock-exchange.js:/mock.js:ro \
|
||||
node:20-alpine node /mock.js
|
||||
|
||||
# 2. AYON_EXCHANGE_URL in .env auf http://mock-exchange:9000/exchange setzen
|
||||
# + docker compose up -d --force-recreate
|
||||
|
||||
# 3. E2E im refboard-ayon-Container ausführen
|
||||
docker cp tests/e2e.sh refboard-ayon:/tmp/e2e.sh
|
||||
docker exec refboard-ayon sh /tmp/e2e.sh
|
||||
```
|
||||
|
||||
Erwartet: 8 Schritte grün (Ticket → Exchange → /me → Board 200 → Reuse 401 →
|
||||
2. User am selben Board → fehlendes Ticket 400 → SPA /b 200).
|
||||
|
||||
## Echter Stack
|
||||
|
||||
Im Produktivbetrieb zeigt `AYON_EXCHANGE_URL` auf das echte Addon
|
||||
(`http://ayon-private-server-1:5000/api/addons/refboard/0.2.0/exchange`).
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/bin/sh
|
||||
# E2E test: ticket exchange -> session -> board access -> single-use -> collab
|
||||
set -e
|
||||
B=http://localhost:8000
|
||||
M=http://mock-exchange:9000
|
||||
|
||||
echo "--- 1. Issue ticket (mock addon) ---"
|
||||
TICKET=$(wget -qO- --header="Content-Type: application/json" \
|
||||
--post-data='{"ayon_user":"niklas","display_name":"Niklas","project":"lumenfjord","task":"assets/chair/modeling"}' \
|
||||
$M/issue | sed 's/.*"ticket":"\([^"]*\)".*/\1/')
|
||||
echo "ticket: ${TICKET:0:8}..."
|
||||
|
||||
echo "--- 2. Browser exchange at RefBoard ---"
|
||||
EX=$(wget -qO- --header="Content-Type: application/json" \
|
||||
--post-data="{\"ticket\":\"$TICKET\"}" $B/api/auth/ayon/exchange)
|
||||
echo "$EX" | head -c 300; echo
|
||||
TOKEN=$(echo "$EX" | sed 's/.*"token":"\([^"]*\)".*/\1/')
|
||||
BOARD_URL=$(echo "$EX" | sed 's/.*"board_url":"\([^"]*\)".*/\1/')
|
||||
echo "board_url: $BOARD_URL"
|
||||
|
||||
echo "--- 3. /me with session ---"
|
||||
wget -qO- --header="Authorization: Bearer $TOKEN" $B/api/auth/me
|
||||
echo
|
||||
|
||||
echo "--- 4. Fetch board ---"
|
||||
BOARD_ID=${BOARD_URL##*/}
|
||||
wget -qO- --header="Authorization: Bearer $TOKEN" $B/api/boards/$BOARD_ID | head -c 200
|
||||
echo
|
||||
|
||||
echo "--- 5. Ticket reuse must fail (single-use) ---"
|
||||
CODE=$(wget -qO- --header="Content-Type: application/json" \
|
||||
--post-data="{\"ticket\":\"$TICKET\"}" $B/api/auth/ayon/exchange 2>&1 | tail -1)
|
||||
echo "reuse response: $CODE"
|
||||
|
||||
echo "--- 6. Second user joins SAME board ---"
|
||||
T2T=$(wget -qO- --header="Content-Type: application/json" \
|
||||
--post-data='{"ayon_user":"artist2","display_name":"Artist Two","project":"lumenfjord","task":"assets/chair/modeling"}' \
|
||||
$M/issue | sed 's/.*"ticket":"\([^"]*\)".*/\1/')
|
||||
EX2=$(wget -qO- --header="Content-Type: application/json" \
|
||||
--post-data="{\"ticket\":\"$T2T\"}" $B/api/auth/ayon/exchange)
|
||||
T2=$(echo "$EX2" | sed 's/.*"token":"\([^"]*\)".*/\1/')
|
||||
echo "user2 board access:"
|
||||
wget -qO- --header="Authorization: Bearer $T2" $B/api/boards/$BOARD_ID | head -c 120
|
||||
echo
|
||||
|
||||
echo "--- 7. Missing ticket -> 400 ---"
|
||||
wget -qO- --header="Content-Type: application/json" --post-data="{}" $B/api/auth/ayon/exchange 2>&1 | tail -1 || true
|
||||
|
||||
echo "--- 8. Frontend SPA route /b served ---"
|
||||
wget -qO- $B/b | head -c 80
|
||||
echo
|
||||
echo "E2E DONE"
|
||||
@@ -0,0 +1,54 @@
|
||||
// Mock AYON addon exchange endpoint for E2E testing.
|
||||
// POST /issue {ayon_user, display_name, project, task} -> {ticket}
|
||||
// POST /exchange {ticket} -> identity payload (single-use, TTL 120s)
|
||||
// NOT FOR PRODUCTION — test double only.
|
||||
const http = require('http');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const tickets = new Map(); // ticket -> {payload, expires}
|
||||
const TTL = 120000;
|
||||
|
||||
const json = (res, code, body) => {
|
||||
const data = JSON.stringify(body);
|
||||
res.writeHead(code, { 'Content-Type': 'application/json' });
|
||||
res.end(data);
|
||||
};
|
||||
|
||||
function readBody(req) {
|
||||
return new Promise((resolve) => {
|
||||
let raw = '';
|
||||
req.on('data', (c) => (raw += c));
|
||||
req.on('end', () => {
|
||||
try { resolve(JSON.parse(raw || '{}')); } catch { resolve({}); }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url, 'http://x');
|
||||
// Simple API key check on /exchange (mirrors the real addon)
|
||||
if (url.pathname === '/exchange') {
|
||||
const key = process.env.REFBOARD_API_KEY || 'testkey';
|
||||
if (req.headers['x-api-key'] !== key) {
|
||||
return json(res, 401, { error: 'bad api key' });
|
||||
}
|
||||
const body = await readBody(req);
|
||||
const t = tickets.get(body.ticket);
|
||||
if (!t || t.expires < Date.now()) {
|
||||
tickets.delete(body.ticket);
|
||||
return json(res, 404, { error: 'unknown or expired ticket' });
|
||||
}
|
||||
tickets.delete(body.ticket); // single-use
|
||||
return json(res, 200, t.payload);
|
||||
}
|
||||
if (url.pathname === '/issue') {
|
||||
const body = await readBody(req);
|
||||
const ticket = crypto.randomBytes(24).toString('hex');
|
||||
tickets.set(ticket, { payload: body, expires: Date.now() + TTL });
|
||||
return json(res, 200, { ticket });
|
||||
}
|
||||
if (url.pathname === '/health') return json(res, 200, { ok: true });
|
||||
json(res, 404, { error: 'not found' });
|
||||
});
|
||||
|
||||
server.listen(9000, () => console.log('mock exchange on 9000'));
|
||||
Reference in New Issue
Block a user