#!/usr/bin/env node import assert from "node:assert/strict"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { createRequire } from "node:module"; const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const runtimeDir = await fs.mkdtemp(path.join(os.tmpdir(), "boss-attachment-analysis-")); const stateFile = path.join(runtimeDir, "data", "boss-state.json"); const require = createRequire(import.meta.url); process.env.BOSS_RUNTIME_ROOT = runtimeDir; process.env.BOSS_STATE_FILE = stateFile; process.env.BOSS_AUTH_AUTO_LOGIN = "0"; const { NextRequest } = require("next/server"); const authLoginRoute = require(path.join(rootDir, ".next/standalone/.next/server/app/api/auth/login/route.js")); const attachmentsRoute = require( path.join(rootDir, ".next/standalone/.next/server/app/api/v1/projects/[projectId]/attachments/route.js"), ); const analyzeRoute = require( path.join( rootDir, ".next/standalone/.next/server/app/api/v1/projects/[projectId]/attachments/[attachmentId]/analyze/route.js", ), ); const loginHandler = authLoginRoute.routeModule.userland.POST; const uploadHandler = attachmentsRoute.routeModule.userland.POST; const analyzeHandler = analyzeRoute.routeModule.userland.POST; async function invokeRoute(handler, url, init = {}, context) { const request = new NextRequest(url, { method: init.method ?? "GET", headers: init.headers, body: init.body, }); return handler(request, context); } function parseCookieValue(setCookieHeader, cookieName) { assert.ok(setCookieHeader, "set-cookie header is missing"); const match = setCookieHeader.match(new RegExp(`${cookieName}=([^;]+)`)); assert.ok(match, `${cookieName} cookie is missing`); return match[1]; } async function loginAsAdmin() { const response = await invokeRoute( loginHandler, "http://localhost/api/auth/login", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ account: "17600003315", password: "boss123456", method: "password", }), }, ); assert.equal(response.status, 200, "login should succeed"); const payload = await response.json(); assert.equal(payload.ok, true, "login payload should be ok"); const cookie = parseCookieValue(response.headers.get("set-cookie"), "boss_session"); return { cookie, payload }; } async function uploadAttachment(cookie, projectId, fileName, type, bytes) { const form = new FormData(); form.set("file", new File([bytes], fileName, { type })); const response = await invokeRoute( uploadHandler, `http://localhost/api/v1/projects/${projectId}/attachments`, { method: "POST", headers: { cookie: `boss_session=${cookie}` }, body: form, }, { params: Promise.resolve({ projectId }) }, ); assert.equal(response.status, 200, `upload ${fileName} should succeed`); return response.json(); } const { cookie } = await loginAsAdmin(); const textUpload = await uploadAttachment( cookie, "master-agent", "analysis-note.txt", "text/plain", Buffer.from("text attachment for automatic analysis"), ); assert.equal(textUpload.attachment.analysisState, "queued_auto", "text attachment should queue automatically"); assert.ok(textUpload.analysisTask, "queued auto attachment should create a master agent task"); assert.equal(textUpload.analysisTask.taskType, "attachment_analysis", "queued task type should be attachment_analysis"); assert.equal( textUpload.analysisTask.attachmentFileName, "analysis-note.txt", "queued task should carry attachment file name", ); const manualUpload = await uploadAttachment( cookie, "master-agent", "manual-binary.bin", "application/octet-stream", Buffer.from([0, 1, 2, 3]), ); assert.equal(manualUpload.attachment.analysisState, "ready_manual", "binary attachment should be manually analyzable"); const analyzeResponse = await invokeRoute( analyzeHandler, `http://localhost/api/v1/projects/master-agent/attachments/${manualUpload.attachment.attachmentId}/analyze`, { method: "POST", headers: { cookie: `boss_session=${cookie}` }, }, { params: Promise.resolve({ projectId: "master-agent", attachmentId: manualUpload.attachment.attachmentId, }), }, ); assert.equal(analyzeResponse.status, 200, "manual analyze should succeed"); const analyzePayload = await analyzeResponse.json(); assert.ok(analyzePayload.taskId, "manual analyze should return a taskId"); assert.ok(analyzePayload.task, "manual analyze should return a task payload"); assert.equal(analyzePayload.task.taskType, "attachment_analysis", "manual analyze task should be attachment_analysis"); assert.equal( analyzePayload.task.attachmentId, manualUpload.attachment.attachmentId, "manual task should link the attachment", ); console.log("attachment analysis validation passed");