import assert from "node:assert/strict"; import { dbAll, dbRun, withTransaction } from "../server/db.mjs"; const api = process.env.AI_DRAMA_API_BASE || "http://127.0.0.1:8787"; const runId = Date.now(); const organizationId = "org-studio-lab"; const workspaceId = "ws-local-aidrama"; const projectId = "thunder-mouth"; const northstarOrganizationId = "org-northstar"; const customerCode = `SMOKE-CUST-${runId}`; const contractNumber = `SMOKE-CTR-${runId}`; const expiredContractNumber = `SMOKE-EXP-${runId}`; let customerId = ""; let contractId = ""; let expiredContractId = ""; async function request(path, options = {}) { const response = await fetch(`${api}${path}`, { ...options, headers: { "content-type": "application/json", ...(options.headers || {}) } }); const payload = await response.json().catch(() => ({})); return { response, payload }; } function expectOk(result, label) { assert.equal(result.response.ok, true, `${label}: ${result.response.status} ${result.payload.detail || result.payload.error || ""}`); return result.payload; } function expectStatus(result, status, label) { assert.equal(result.response.status, status, `${label}: ${result.response.status} ${result.payload.detail || result.payload.error || ""}`); return result.payload; } async function login(email) { const result = expectOk(await request("/api/auth/login", { method: "POST", body: JSON.stringify({ email, password: "Demo@123456" }) }), `${email} login`); return { authorization: `Bearer ${result.session.token}` }; } function futureIso(days) { return new Date(Date.now() + days * 86400000).toISOString(); } function pastIso(days) { return new Date(Date.now() - days * 86400000).toISOString(); } function contractBody(number, title, overrides = {}) { return { customerId, contractNumber: number, title, contractType: "production_license", status: "active", approvalStatus: "approved", effectiveAt: pastIso(10), expiresAt: futureIso(365), signedAt: pastIso(9), currency: "CNY", amount: 0, licenseScope: { channels: ["local-file", "private-delivery-portal"], territories: ["CN"], deliverables: ["vertical-video", "delivery-manifest"], platforms: ["private-preview", "internal-review"], exclusive: false, commercialUse: true, language: ["zh-CN"] }, rights: { originalStory: true, derivativeProduction: true, aiGeneratedAssetsAllowed: true, voiceCloneAllowed: false, fixedVoiceRequired: true, singleFramePolicyRequired: true }, deliveryTerms: { customerPortalAllowed: true, requireClearanceCertificate: true, maxAccessDays: 14 }, evidence: { contractRef: `smoke://contract/${number}`, rightsEvidenceRef: `smoke://rights/${number}`, signedCopyPath: `storage/contracts/smoke/${number}.json` }, risk: { paidCloudAllowed: false, publicModelAllowed: false, rightsReview: "passed" }, approvalNote: "smoke test contract", ...overrides }; } const ownerAuth = await login("producer@local.test"); const writerAuth = await login("writer@local.test"); const northstarAuth = await login("producer2@local.test"); const headers = { ...ownerAuth, "x-organization-id": organizationId, "x-workspace-id": workspaceId, "x-project-id": projectId }; const writerHeaders = { ...writerAuth, "x-organization-id": organizationId, "x-workspace-id": workspaceId, "x-project-id": projectId }; const northstarHeaders = { ...northstarAuth, "x-organization-id": northstarOrganizationId, "x-workspace-id": "ws-northstar-main", "x-project-id": "northstar-pilot" }; try { const seeded = expectOk(await request(`/api/organizations/${organizationId}/contracts`, { headers }), "owner lists seeded contracts"); assert.ok(seeded.customers.some((customer) => customer.id === "cust-xinghe-release"), "seeded customer must be visible"); assert.ok(seeded.contracts.some((contract) => contract.id === "contract-xh-rain-night-2026"), "seeded contract must be visible"); assert.ok(seeded.projects.some((project) => project.id === projectId), "current project must be selectable for binding"); const seededClearance = expectOk(await request(`/api/organizations/${organizationId}/contracts/clearance?projectId=${encodeURIComponent(projectId)}&channelKind=private-delivery-portal`, { headers }), "seeded contract clearance"); assert.equal(seededClearance.clearance.status, "pass", "seeded thunder-mouth contract clearance must pass"); assert.ok(seededClearance.clearance.bindings.some((binding) => binding.contract.id === "contract-xh-rain-night-2026"), "clearance must include seeded contract binding"); expectStatus(await request(`/api/organizations/${organizationId}/contracts`, { headers: writerHeaders }), 403, "writer cannot list contracts"); expectStatus(await request(`/api/organizations/${organizationId}/contracts/contract-ns-pilot-2026/bind-project`, { method: "POST", headers, body: JSON.stringify({ projectId, usageMode: "supplemental-rights" }) }), 404, "studio owner cannot bind northstar contract"); const northstar = expectOk(await request(`/api/organizations/${northstarOrganizationId}/contracts`, { headers: northstarHeaders }), "northstar lists own contracts"); assert.ok(northstar.contracts.some((contract) => contract.id === "contract-ns-pilot-2026"), "northstar seeded contract must be visible inside its organization"); assert.ok(!northstar.contracts.some((contract) => contract.id === "contract-xh-rain-night-2026"), "northstar must not see studio contracts"); const customer = expectOk(await request(`/api/organizations/${organizationId}/customers`, { method: "POST", headers, body: JSON.stringify({ name: `烟测发行客户 ${runId}`, legalName: `烟测发行客户 ${runId} 有限公司`, code: customerCode, customerType: "distributor", status: "active", industry: "AI 短剧发行", region: "CN", billingEmail: "smoke@example.local", tags: ["smoke", "contracts"], notes: "合同工作台 smoke 自动创建" }) }), "create customer"); customerId = customer.customer.id; assert.equal(customer.customer.code, customerCode, "customer code must be persisted"); const contact = expectOk(await request(`/api/organizations/${organizationId}/customers/${encodeURIComponent(customerId)}/contacts`, { method: "POST", headers, body: JSON.stringify({ name: "烟测制片窗口", role: "交付验收", email: "acceptance@example.local", isPrimary: true }) }), "create customer contact"); assert.equal(contact.customer.contacts[0].isPrimary, true, "primary contact must be persisted"); const contract = expectOk(await request(`/api/organizations/${organizationId}/contracts`, { method: "POST", headers, body: JSON.stringify(contractBody(contractNumber, `烟测商用授权 ${runId}`)) }), "create active approved contract"); contractId = contract.contract.id; assert.equal(contract.contract.contractNumber, contractNumber, "contract number must be persisted"); const bound = expectOk(await request(`/api/organizations/${organizationId}/contracts/${encodeURIComponent(contractId)}/bind-project`, { method: "POST", headers, body: JSON.stringify({ projectId, usageMode: "supplemental-rights", status: "active", notes: "smoke supplemental rights" }) }), "bind active contract to project"); assert.equal(bound.binding.projectId, projectId, "binding project must be persisted"); assert.equal(bound.clearance.status, "pass", "valid supplemental contract must not block clearance"); const expired = expectOk(await request(`/api/organizations/${organizationId}/contracts`, { method: "POST", headers, body: JSON.stringify(contractBody(expiredContractNumber, `烟测过期授权 ${runId}`, { expiresAt: pastIso(1) })) }), "create expired contract"); expiredContractId = expired.contract.id; expectOk(await request(`/api/organizations/${organizationId}/contracts/${encodeURIComponent(expiredContractId)}/bind-project`, { method: "POST", headers, body: JSON.stringify({ projectId, usageMode: "supplemental-rights", status: "active", notes: "smoke expired rights" }) }), "bind expired contract"); const blocked = expectOk(await request(`/api/organizations/${organizationId}/contracts/clearance?projectId=${encodeURIComponent(projectId)}&channelKind=private-delivery-portal`, { headers }), "expired contract blocks clearance"); assert.equal(blocked.clearance.status, "blocked", "expired active binding must block project contract clearance"); assert.ok(blocked.clearance.blockers.some((item) => item.code === "contract_expired"), "clearance blockers must include contract_expired"); const events = dbAll("SELECT event_type FROM contract_license_events WHERE organization_id = ? AND (contract_id = ? OR contract_id = ?)", [organizationId, contractId, expiredContractId]); assert.ok(events.some((event) => event.event_type === "contract.created"), "contract creation must write license event"); assert.ok(events.some((event) => event.event_type === "contract.project_binding.created"), "project binding must write license event"); console.log(`contracts smoke passed: ${contractId} / ${expiredContractId}`); } finally { withTransaction(() => { for (const id of [contractId, expiredContractId].filter(Boolean)) { dbRun("DELETE FROM contract_license_events WHERE contract_id = ?", [id]); dbRun("DELETE FROM contract_project_bindings WHERE contract_id = ?", [id]); dbRun("DELETE FROM audit_logs WHERE target_id = ? OR metadata_json LIKE ?", [id, `%${id}%`]); dbRun("DELETE FROM production_contracts WHERE id = ?", [id]); } if (customerId) { dbRun("DELETE FROM customer_contacts WHERE customer_id = ?", [customerId]); dbRun("DELETE FROM contract_license_events WHERE customer_id = ?", [customerId]); dbRun("DELETE FROM audit_logs WHERE target_id = ? OR metadata_json LIKE ?", [customerId, `%${customerId}%`]); dbRun("DELETE FROM customers WHERE id = ?", [customerId]); } dbRun("DELETE FROM customers WHERE organization_id = ? AND code = ?", [organizationId, customerCode]); dbRun("DELETE FROM production_contracts WHERE organization_id = ? AND contract_number IN (?, ?)", [organizationId, contractNumber, expiredContractNumber]); }); }