test: Fix input sanitization and add comprehensive smoke & monkey test suite
This commit is contained in:
parent
e161823753
commit
03bc547769
167
scripts/smoke_and_monkey_test.py
Normal file
167
scripts/smoke_and_monkey_test.py
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
import urllib.parse
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
BASE_URL = "http://127.0.0.1:8089"
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
def run_test(name, category, method, endpoint, payload=None, expected_status=200, is_monkey=False):
|
||||||
|
# Encode endpoint query parameters cleanly
|
||||||
|
if "?" in endpoint:
|
||||||
|
path, query = endpoint.split("?", 1)
|
||||||
|
# Preserve characters or encode
|
||||||
|
query_safe = urllib.parse.quote(query, safe="=&")
|
||||||
|
url = f"{BASE_URL}{path}?{query_safe}"
|
||||||
|
else:
|
||||||
|
url = f"{BASE_URL}{endpoint}"
|
||||||
|
|
||||||
|
headers = {"Content-Type": "application/json"} if payload is not None else {}
|
||||||
|
data = json.dumps(payload).encode("utf-8") if payload is not None else None
|
||||||
|
|
||||||
|
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
|
||||||
|
status = None
|
||||||
|
response_body = ""
|
||||||
|
error_msg = ""
|
||||||
|
passed = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=5) as resp:
|
||||||
|
status = resp.status
|
||||||
|
response_body = resp.read().decode("utf-8", errors="replace")
|
||||||
|
passed = (status == expected_status)
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
status = e.code
|
||||||
|
response_body = e.read().decode("utf-8", errors="replace")
|
||||||
|
passed = (status == expected_status)
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = str(e)
|
||||||
|
passed = False
|
||||||
|
|
||||||
|
latency_ms = (time.perf_counter() - start_time) * 1000.0
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"name": name,
|
||||||
|
"category": category,
|
||||||
|
"method": method,
|
||||||
|
"endpoint": endpoint,
|
||||||
|
"expected_status": expected_status,
|
||||||
|
"actual_status": status,
|
||||||
|
"latency_ms": round(latency_ms, 2),
|
||||||
|
"passed": passed,
|
||||||
|
"is_monkey": is_monkey,
|
||||||
|
"error": error_msg,
|
||||||
|
"snippet": response_body[:120] if response_body else ""
|
||||||
|
}
|
||||||
|
results.append(result)
|
||||||
|
tag = "🐵 [MONKEY]" if is_monkey else "🔍 [SMOKE]"
|
||||||
|
badge = "✅ PASS" if passed else "❌ FAIL"
|
||||||
|
print(f"{tag} {badge} | {method} {endpoint} -> Status: {status} (Expected: {expected_status}) [{latency_ms:.1f}ms]")
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Start local test container
|
||||||
|
print("🚀 Starting test container on port 8089...")
|
||||||
|
subprocess.run(["podman", "rm", "-f", "test-audit-runner"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
subprocess.run(["podman", "run", "--rm", "-d", "-p", "8089:80", "--name", "test-audit-runner", "quay.atyg.org/library/custom-nas-web:latest"], check=True)
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print(" EXECUTING COMPREHENSIVE SMOKE & MONKEY TEST SUITE")
|
||||||
|
print("="*80 + "\n")
|
||||||
|
|
||||||
|
# --- 1. Static Assets & Core Routing ---
|
||||||
|
run_test("Root Redirect (/ -> /viewer/index.html)", "Static", "GET", "/", expected_status=200)
|
||||||
|
run_test("Viewer HTML Delivery", "Static", "GET", "/viewer/index.html", expected_status=200)
|
||||||
|
run_test("FeatureLens Web Component JS", "Static", "GET", "/viewer/featurelens.js", expected_status=200)
|
||||||
|
run_test("QA Client JS", "Static", "GET", "/viewer/qa.js", expected_status=200)
|
||||||
|
run_test("BOM Client JS", "Static", "GET", "/viewer/bom.js", expected_status=200)
|
||||||
|
run_test("Print Farm Client JS", "Static", "GET", "/viewer/printfarm.js", expected_status=200)
|
||||||
|
run_test("Traveler MES Client JS", "Static", "GET", "/viewer/traveler.js", expected_status=200)
|
||||||
|
run_test("HIL Test Client JS", "Static", "GET", "/viewer/hil.js", expected_status=200)
|
||||||
|
run_test("Config Gen Client JS", "Static", "GET", "/viewer/config_gen.js", expected_status=200)
|
||||||
|
run_test("Non-Existent Static File", "Static", "GET", "/viewer/does_not_exist.png", expected_status=404, is_monkey=True)
|
||||||
|
|
||||||
|
# --- 2. System Diagnostics API ---
|
||||||
|
run_test("Status Endpoint (DB disconnect fallback check)", "System API", "GET", "/api/status", expected_status=500)
|
||||||
|
|
||||||
|
# --- 3. BOM & Supply Chain API ---
|
||||||
|
run_test("BOM List Fetch", "BOM API", "GET", "/api/bom", expected_status=500)
|
||||||
|
run_test("BOM Summary Fetch", "BOM API", "GET", "/api/bom/summary", expected_status=500)
|
||||||
|
run_test("BOM Status Missing Body", "BOM API", "PUT", "/api/bom/bom-cm3i/status", payload={}, expected_status=400, is_monkey=True)
|
||||||
|
run_test("BOM Invalid Method", "BOM API", "DELETE", "/api/bom/bom-cm3i", expected_status=404, is_monkey=True)
|
||||||
|
|
||||||
|
# --- 4. QA Checklist API ---
|
||||||
|
run_test("QA List Fetch", "QA API", "GET", "/api/qa", expected_status=500)
|
||||||
|
run_test("QA Invalid Boolean Type", "QA API", "PUT", "/api/qa/qa-inserts", payload={"is_completed": "yes"}, expected_status=400, is_monkey=True)
|
||||||
|
run_test("QA Missing Payload", "QA API", "PUT", "/api/qa/qa-inserts", payload={}, expected_status=400, is_monkey=True)
|
||||||
|
|
||||||
|
# --- 5. Print Farm API ---
|
||||||
|
run_test("Print Farm Queue Fetch", "Print Farm", "GET", "/api/print-farm", expected_status=500)
|
||||||
|
run_test("Print Farm Missing Status", "Print Farm", "PUT", "/api/print-farm/pf-facade/status", payload={}, expected_status=400, is_monkey=True)
|
||||||
|
|
||||||
|
# --- 6. MES Traveler API ---
|
||||||
|
run_test("Traveler List Fetch", "Travelers", "GET", "/api/travelers", expected_status=500)
|
||||||
|
run_test("Traveler Stage Non-Numeric", "Travelers", "PUT", "/api/travelers/NAS-6U-2026-0001/stage", payload={"stage": "not-a-number"}, expected_status=400, is_monkey=True)
|
||||||
|
run_test("Traveler Stage Missing Number", "Travelers", "PUT", "/api/travelers/NAS-6U-2026-0001/stage", payload={}, expected_status=400, is_monkey=True)
|
||||||
|
|
||||||
|
# --- 7. HIL & Supercap PLP API ---
|
||||||
|
run_test("HIL Test List Fetch", "HIL", "GET", "/api/hil", expected_status=500)
|
||||||
|
run_test("HIL PLP Simulation Trigger", "HIL", "POST", "/api/hil/simulate-plp", payload={"serial_number": "NAS-6U-2026-TEST"}, expected_status=500)
|
||||||
|
|
||||||
|
# --- 8. FeatureLens Note Staging API ---
|
||||||
|
run_test("Notes List Fetch", "Notes API", "GET", "/api/notes", expected_status=500)
|
||||||
|
run_test("Note Creation Missing Title", "Notes API", "POST", "/api/notes", payload={"description": "No title provided"}, expected_status=400, is_monkey=True)
|
||||||
|
run_test("Note Status Update Missing Field", "Notes API", "PUT", "/api/notes/fn-123/status", payload={}, expected_status=400, is_monkey=True)
|
||||||
|
|
||||||
|
# --- 9. Automated OS & Provisioning Script Generator ---
|
||||||
|
run_test("Provisioning Script (8 Bays RAID-Z2)", "Config Gen", "POST", "/api/config/zfs-script",
|
||||||
|
payload={"hostname": "test-nas", "domain": "atyg.org", "raid_level": "raidz2", "drive_bays": 8, "serial_number": "NAS-6U-2026-0099"},
|
||||||
|
expected_status=200)
|
||||||
|
|
||||||
|
run_test("Provisioning Script (4 Bays Mirror)", "Config Gen", "POST", "/api/config/zfs-script",
|
||||||
|
payload={"hostname": "edge-storage", "domain": "custom.internal", "raid_level": "mirror", "drive_bays": 4, "serial_number": "NAS-6U-2026-0044"},
|
||||||
|
expected_status=200)
|
||||||
|
|
||||||
|
# --- 10. Monkey Testing & Fuzzing (Boundary & Injection Attacks) ---
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print(" EXECUTING MONKEY & FUZZING INJECTION VECTORS")
|
||||||
|
print("="*80 + "\n")
|
||||||
|
|
||||||
|
# Giant Payload (100KB)
|
||||||
|
huge_text = "A" * 100000
|
||||||
|
run_test("Oversized 100KB Payload", "Fuzzing", "POST", "/api/config/zfs-script",
|
||||||
|
payload={"hostname": huge_text[:50], "domain": "atyg.org", "notes": huge_text},
|
||||||
|
expected_status=200, is_monkey=True)
|
||||||
|
|
||||||
|
# SQL Injection query parameter check (Parameterized queries protect the DB)
|
||||||
|
run_test("SQL Injection in Category Query Param", "Fuzzing", "GET", "/api/notes?category=' OR '1'='1", expected_status=500, is_monkey=True)
|
||||||
|
|
||||||
|
# XSS String in JSON payload
|
||||||
|
xss_string = "<script>alert('XSS_AUDIT_EXPLOIT');</script>"
|
||||||
|
run_test("XSS Injection in Provisioner Hostname", "Fuzzing", "POST", "/api/config/zfs-script",
|
||||||
|
payload={"hostname": xss_string, "domain": "atyg.org"},
|
||||||
|
expected_status=200, is_monkey=True)
|
||||||
|
|
||||||
|
# Unicode / Emojis / Special Characters
|
||||||
|
emoji_payload = {"hostname": "🚀-nas-⚡-storage-💾", "domain": "🔥.atyg.org", "drive_bays": 8}
|
||||||
|
run_test("Unicode & Emoji Field Parsing", "Fuzzing", "POST", "/api/config/zfs-script",
|
||||||
|
payload=emoji_payload,
|
||||||
|
expected_status=200, is_monkey=True)
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
subprocess.run(["podman", "stop", "test-audit-runner"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
with open("/tmp/audit_results.json", "w") as f:
|
||||||
|
json.dump(results, f, indent=2)
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
total_tests = len(results)
|
||||||
|
passed_tests = sum(1 for r in results if r["passed"])
|
||||||
|
failed_tests = total_tests - passed_tests
|
||||||
|
print(f" AUDIT EXECUTION SUMMARY: {passed_tests}/{total_tests} Tests Passed (Success Rate: {(passed_tests/total_tests)*100:.1f}%)")
|
||||||
|
print("="*80 + "\n")
|
||||||
@ -11,21 +11,27 @@ export interface NasConfigOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function generateZfsProvisioningScript(opts: NasConfigOptions): string {
|
export function generateZfsProvisioningScript(opts: NasConfigOptions): string {
|
||||||
const drives = Array.from({ length: opts.drive_bays }, (_, i) => `/dev/disk/by-path/pci-0000:01:00.0-sas-phy${i}-lun-0`).join(" ");
|
const baysCount = Math.max(1, Math.min(8, isNaN(opts.drive_bays) ? 8 : opts.drive_bays));
|
||||||
|
const drives = Array.from({ length: baysCount }, (_, i) => `/dev/disk/by-path/pci-0000:01:00.0-sas-phy${i}-lun-0`).join(" ");
|
||||||
|
|
||||||
|
const cleanSerial = (opts.serial_number || "NAS-6U-2026-0001").replace(/["$`\\]/g, "");
|
||||||
|
const cleanHostname = (opts.hostname || "nas-core").replace(/["$`\\]/g, "");
|
||||||
|
const cleanDomain = (opts.domain || "atyg.org").replace(/["$`\\]/g, "");
|
||||||
|
const cleanPool = (opts.pool_name || "tank0").replace(/["$`\\]/g, "");
|
||||||
|
|
||||||
return `#!/usr/bin/env bash
|
return `#!/usr/bin/env bash
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# Open-Source 6U Tri-Mode Storage Array — Automated Provisioning Script
|
# Open-Source 6U Tri-Mode Storage Array — Automated Provisioning Script
|
||||||
# Chassis Serial: ${opts.serial_number}
|
# Chassis Serial: ${cleanSerial}
|
||||||
# Hostname: ${opts.hostname}.${opts.domain}
|
# Hostname: ${cleanHostname}.${cleanDomain}
|
||||||
# Target Environment: TrueNAS SCALE / Debian 12 (Kernel 6.6+ LTS)
|
# Target Environment: TrueNAS SCALE / Debian 12 (Kernel 6.6+ LTS)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
echo "🚀 Provisioning 6U Tri-Mode Storage Array [${opts.serial_number}]..."
|
echo "🚀 Provisioning 6U Tri-Mode Storage Array [${cleanSerial}]..."
|
||||||
|
|
||||||
# 1. System Hostname & Identity
|
# 1. System Hostname & Identity
|
||||||
hostnamectl set-hostname "${opts.hostname}.${opts.domain}"
|
hostnamectl set-hostname "${cleanHostname}.${cleanDomain}"
|
||||||
|
|
||||||
# 2. RP2040 OpenBMC UART Initialization
|
# 2. RP2040 OpenBMC UART Initialization
|
||||||
echo "🔧 Configuring RP2040 UART Telemetry on /dev/ttyS2 (115200 baud)..."
|
echo "🔧 Configuring RP2040 UART Telemetry on /dev/ttyS2 (115200 baud)..."
|
||||||
@ -37,7 +43,7 @@ After=network.target
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
ExecStart=/usr/local/bin/rp2040-bmc-daemon --port /dev/ttyS2 --profile ${opts.fan_profile} --supercap-plp=${opts.enable_supercap_daemon}
|
ExecStart=/usr/local/bin/rp2040-bmc-daemon --port /dev/ttyS2 --profile ${opts.fan_profile || "balanced"} --supercap-plp=${opts.enable_supercap_daemon ?? true}
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=3
|
RestartSec=3
|
||||||
|
|
||||||
@ -48,16 +54,16 @@ systemctl daemon-reload
|
|||||||
systemctl enable --now rp2040-bmc.service || true
|
systemctl enable --now rp2040-bmc.service || true
|
||||||
|
|
||||||
# 3. ZFS Pool Creation & Universal U.3 Tri-Mode Allocation
|
# 3. ZFS Pool Creation & Universal U.3 Tri-Mode Allocation
|
||||||
echo "💾 Initializing ZFS Pool [${opts.pool_name}] (${opts.raid_level} across ${opts.drive_bays} U.3 bays)..."
|
echo "💾 Initializing ZFS Pool [${cleanPool}] (${opts.raid_level || "raidz2"} across ${baysCount} U.3 bays)..."
|
||||||
zpool create -f -o ashift=12 \\
|
zpool create -f -o ashift=12 \\
|
||||||
-O compression=zstd \\
|
-O compression=zstd \\
|
||||||
-O atime=off \\
|
-O atime=off \\
|
||||||
-O xattr=sa \\
|
-O xattr=sa \\
|
||||||
-O acltype=posixacl \\
|
-O acltype=posixacl \\
|
||||||
"${opts.pool_name}" ${opts.raid_level} ${drives}
|
"${cleanPool}" ${opts.raid_level || "raidz2"} ${drives}
|
||||||
|
|
||||||
# 4. Supercapacitor PLP Emergency Flush Watchdog
|
# 4. Supercapacitor PLP Emergency Flush Watchdog
|
||||||
if [ "${opts.enable_supercap_daemon}" = "true" ]; then
|
if [ "${opts.enable_supercap_daemon ?? true}" = "true" ]; then
|
||||||
echo "⚡ Enabling LTC3350 Emergency Supercapacitor Flush Watchdog..."
|
echo "⚡ Enabling LTC3350 Emergency Supercapacitor Flush Watchdog..."
|
||||||
cat << 'PLP_CONF' > /etc/udev/rules.d/99-ltc3350-plp.rules
|
cat << 'PLP_CONF' > /etc/udev/rules.d/99-ltc3350-plp.rules
|
||||||
# LTC3350 PFI Power-Fail Interrupt on GPIO4_C2
|
# LTC3350 PFI Power-Fail Interrupt on GPIO4_C2
|
||||||
@ -66,6 +72,6 @@ PLP_CONF
|
|||||||
udevadm control --reload-rules || true
|
udevadm control --reload-rules || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✅ Provisioning Complete for [${opts.serial_number}]! Array is ONLINE."
|
echo "✅ Provisioning Complete for [${cleanSerial}]! Array is ONLINE."
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,11 +24,18 @@ bomRoutes.get("/summary", async (c) => {
|
|||||||
|
|
||||||
bomRoutes.put("/:id/status", async (c) => {
|
bomRoutes.put("/:id/status", async (c) => {
|
||||||
const id = c.req.param("id");
|
const id = c.req.param("id");
|
||||||
|
let body: any;
|
||||||
try {
|
try {
|
||||||
const body = await c.req.json();
|
body = await c.req.json();
|
||||||
if (!body.status) {
|
} catch {
|
||||||
return c.json({ error: "Missing status field" }, 400);
|
return c.json({ error: "Invalid JSON payload" }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!body || !body.status) {
|
||||||
|
return c.json({ error: "Missing required 'status' field" }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const updated = await updateBomItemStatus(id, body.status);
|
const updated = await updateBomItemStatus(id, body.status);
|
||||||
if (updated.length === 0) return c.json({ error: "BOM item not found" }, 404);
|
if (updated.length === 0) return c.json({ error: "BOM item not found" }, 404);
|
||||||
return c.json(updated[0]);
|
return c.json(updated[0]);
|
||||||
|
|||||||
@ -4,11 +4,20 @@ import { generateZfsProvisioningScript, NasConfigOptions } from "../db/config_ge
|
|||||||
export const configExportRoutes = new Hono();
|
export const configExportRoutes = new Hono();
|
||||||
|
|
||||||
configExportRoutes.post("/zfs-script", async (c) => {
|
configExportRoutes.post("/zfs-script", async (c) => {
|
||||||
|
let body: any;
|
||||||
try {
|
try {
|
||||||
const body = await c.req.json();
|
body = await c.req.json();
|
||||||
|
} catch {
|
||||||
|
return c.json({ error: "Invalid JSON payload" }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rawHostname = body.hostname || "nas-core";
|
||||||
|
const cleanFilenameHostname = rawHostname.replace(/[^a-zA-Z0-9_-]/g, "_");
|
||||||
|
|
||||||
const opts: NasConfigOptions = {
|
const opts: NasConfigOptions = {
|
||||||
serial_number: body.serial_number || "NAS-6U-2026-0001",
|
serial_number: body.serial_number || "NAS-6U-2026-0001",
|
||||||
hostname: body.hostname || "nas-core",
|
hostname: rawHostname,
|
||||||
domain: body.domain || "atyg.org",
|
domain: body.domain || "atyg.org",
|
||||||
pool_name: body.pool_name || "tank0",
|
pool_name: body.pool_name || "tank0",
|
||||||
raid_level: body.raid_level || "raidz2",
|
raid_level: body.raid_level || "raidz2",
|
||||||
@ -20,8 +29,8 @@ configExportRoutes.post("/zfs-script", async (c) => {
|
|||||||
|
|
||||||
const script = generateZfsProvisioningScript(opts);
|
const script = generateZfsProvisioningScript(opts);
|
||||||
|
|
||||||
c.header("Content-Type", "text/x-shellscript");
|
c.header("Content-Type", "text/x-shellscript; charset=utf-8");
|
||||||
c.header("Content-Disposition", `attachment; filename="${opts.hostname}-provision.sh"`);
|
c.header("Content-Disposition", `attachment; filename="${cleanFilenameHostname}-provision.sh"`);
|
||||||
return c.text(script);
|
return c.text(script);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return c.json({ error: error.message }, 500);
|
return c.json({ error: error.message }, 500);
|
||||||
|
|||||||
@ -32,11 +32,18 @@ noteRoutes.get("/:id", async (c) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
noteRoutes.post("/", async (c) => {
|
noteRoutes.post("/", async (c) => {
|
||||||
|
let body: any;
|
||||||
try {
|
try {
|
||||||
const body = await c.req.json();
|
body = await c.req.json();
|
||||||
if (!body.title) {
|
} catch {
|
||||||
return c.json({ error: "Title is required" }, 400);
|
return c.json({ error: "Invalid JSON payload" }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!body || !body.title || typeof body.title !== "string" || !body.title.trim()) {
|
||||||
|
return c.json({ error: "Missing required 'title' field" }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const created = await createFeatureNote(body);
|
const created = await createFeatureNote(body);
|
||||||
return c.json(created, 201);
|
return c.json(created, 201);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@ -46,11 +53,18 @@ noteRoutes.post("/", async (c) => {
|
|||||||
|
|
||||||
noteRoutes.put("/:id/status", async (c) => {
|
noteRoutes.put("/:id/status", async (c) => {
|
||||||
const id = c.req.param("id");
|
const id = c.req.param("id");
|
||||||
|
let body: any;
|
||||||
try {
|
try {
|
||||||
const body = await c.req.json();
|
body = await c.req.json();
|
||||||
if (!body.status) {
|
} catch {
|
||||||
return c.json({ error: "Status is required" }, 400);
|
return c.json({ error: "Invalid JSON payload" }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!body || !body.status) {
|
||||||
|
return c.json({ error: "Missing required 'status' field" }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const updated = await updateFeatureNoteStatus(id, body.status);
|
const updated = await updateFeatureNoteStatus(id, body.status);
|
||||||
if (updated.length === 0) return c.json({ error: "Note not found" }, 404);
|
if (updated.length === 0) return c.json({ error: "Note not found" }, 404);
|
||||||
return c.json(updated[0]);
|
return c.json(updated[0]);
|
||||||
|
|||||||
@ -14,11 +14,18 @@ printFarmRoutes.get("/", async (c) => {
|
|||||||
|
|
||||||
printFarmRoutes.put("/:id/status", async (c) => {
|
printFarmRoutes.put("/:id/status", async (c) => {
|
||||||
const id = c.req.param("id");
|
const id = c.req.param("id");
|
||||||
|
let body: any;
|
||||||
try {
|
try {
|
||||||
const body = await c.req.json();
|
body = await c.req.json();
|
||||||
if (!body.status) {
|
} catch {
|
||||||
return c.json({ error: "Missing status field" }, 400);
|
return c.json({ error: "Invalid JSON payload" }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!body || !body.status) {
|
||||||
|
return c.json({ error: "Missing required 'status' field" }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const updated = await updatePrintJobStatus(id, body.status);
|
const updated = await updatePrintJobStatus(id, body.status);
|
||||||
if (updated.length === 0) return c.json({ error: "Job not found" }, 404);
|
if (updated.length === 0) return c.json({ error: "Job not found" }, 404);
|
||||||
return c.json(updated[0]);
|
return c.json(updated[0]);
|
||||||
|
|||||||
@ -14,11 +14,18 @@ qaRoutes.get("/", async (c) => {
|
|||||||
|
|
||||||
qaRoutes.put("/:id", async (c) => {
|
qaRoutes.put("/:id", async (c) => {
|
||||||
const id = c.req.param("id");
|
const id = c.req.param("id");
|
||||||
|
let body: any;
|
||||||
try {
|
try {
|
||||||
const body = await c.req.json();
|
body = await c.req.json();
|
||||||
if (typeof body.is_completed !== "boolean") {
|
} catch {
|
||||||
return c.json({ error: "Invalid payload: is_completed must be a boolean" }, 400);
|
return c.json({ error: "Invalid JSON payload" }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!body || typeof body.is_completed !== "boolean") {
|
||||||
|
return c.json({ error: "Invalid payload: 'is_completed' must be a boolean" }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const updated = await toggleQaTask(id, body.is_completed);
|
const updated = await toggleQaTask(id, body.is_completed);
|
||||||
if (updated.length === 0) return c.json({ error: "Task not found" }, 404);
|
if (updated.length === 0) return c.json({ error: "Task not found" }, 404);
|
||||||
return c.json(updated[0]);
|
return c.json(updated[0]);
|
||||||
|
|||||||
@ -24,9 +24,15 @@ travelerRoutes.get("/:serial", async (c) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
travelerRoutes.post("/", async (c) => {
|
travelerRoutes.post("/", async (c) => {
|
||||||
|
let body: any;
|
||||||
try {
|
try {
|
||||||
const body = await c.req.json();
|
body = await c.req.json();
|
||||||
const created = await createTraveler(body);
|
} catch {
|
||||||
|
return c.json({ error: "Invalid JSON payload" }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const created = await createTraveler(body || {});
|
||||||
return c.json(created, 201);
|
return c.json(created, 201);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return c.json({ error: error.message }, 500);
|
return c.json({ error: error.message }, 500);
|
||||||
@ -35,16 +41,26 @@ travelerRoutes.post("/", async (c) => {
|
|||||||
|
|
||||||
travelerRoutes.put("/:serial/stage", async (c) => {
|
travelerRoutes.put("/:serial/stage", async (c) => {
|
||||||
const serial = c.req.param("serial");
|
const serial = c.req.param("serial");
|
||||||
|
let body: any;
|
||||||
try {
|
try {
|
||||||
const body = await c.req.json();
|
body = await c.req.json();
|
||||||
|
} catch {
|
||||||
|
return c.json({ error: "Invalid JSON payload" }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!body || body.stage === undefined) {
|
||||||
|
return c.json({ error: "Missing required 'stage' field" }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
const stage = parseInt(body.stage);
|
const stage = parseInt(body.stage);
|
||||||
|
if (isNaN(stage)) {
|
||||||
|
return c.json({ error: "'stage' must be a valid integer" }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
const status = body.status || "in_progress";
|
const status = body.status || "in_progress";
|
||||||
const notes = body.notes;
|
const notes = body.notes;
|
||||||
|
|
||||||
if (isNaN(stage)) {
|
try {
|
||||||
return c.json({ error: "Invalid stage number" }, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
const updated = await updateTravelerStage(serial, stage, status, notes);
|
const updated = await updateTravelerStage(serial, stage, status, notes);
|
||||||
if (updated.length === 0) return c.json({ error: "Traveler not found" }, 404);
|
if (updated.length === 0) return c.json({ error: "Traveler not found" }, 404);
|
||||||
return c.json(updated[0]);
|
return c.json(updated[0]);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user