| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- import { expect, test } from "bun:test"
- import fs from "node:fs/promises"
- import os from "node:os"
- import path from "node:path"
- import { OPENCODE_VERSION } from "../src/version"
- import { writeExport } from "../src/commands/handlers/export"
- const info = {
- id: "ses_export_test",
- projectID: "global",
- cost: 0,
- tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
- time: { created: 1, updated: 2 },
- title: "Exported session",
- location: { directory: "/project" },
- }
- const transfer = {
- info,
- messages: [
- { id: "msg_first", type: "user", text: "First", time: { created: 1 } },
- { id: "msg_second", type: "user", text: "Second", time: { created: 2 } },
- ],
- }
- const sanitizedTransfer = {
- info: {
- ...info,
- title: "[redacted:session-title:ses_export_test]",
- location: { directory: "/[redacted:session-directory:ses_export_test]" },
- },
- messages: [
- {
- id: "msg_first",
- type: "user",
- text: "[redacted:text:msg_first]",
- time: { created: 1 },
- },
- {
- id: "msg_second",
- type: "user",
- text: "[redacted:text:msg_second]",
- time: { created: 2 },
- },
- ],
- }
- const health = () => Response.json({ healthy: true, version: OPENCODE_VERSION, pid: process.pid })
- function run(args: string[], stdin?: string) {
- const child = Bun.spawn([process.execPath, "run", "src/index.ts", ...args], {
- cwd: path.join(import.meta.dir, ".."),
- stdin: stdin === undefined ? undefined : new Blob([stdin]),
- stdout: "pipe",
- stderr: "pipe",
- })
- return Promise.all([new Response(child.stdout).text(), new Response(child.stderr).text(), child.exited])
- }
- test("export is raw by default and supports explicit sanitization", async () => {
- const sanitization: string[] = []
- const server = Bun.serve({
- port: 0,
- fetch(request) {
- const url = new URL(request.url)
- if (url.pathname === "/api/health") return health()
- if (url.pathname === `/api/session/${info.id}`) return Response.json({ data: info })
- if (url.pathname === `/api/session/${info.id}/export`) {
- sanitization.push(url.searchParams.get("sanitize") ?? "")
- return Response.json({ data: url.searchParams.get("sanitize") === "true" ? sanitizedTransfer : transfer })
- }
- return new Response("Not found", { status: 404 })
- },
- })
- try {
- const [stdout, , exitCode] = await run(["export", "-s", info.id, "--server", server.url.toString()])
- const exported = JSON.parse(stdout)
- expect(exitCode).toBe(0)
- expect(exported).toEqual(transfer)
- const [sanitized, , sanitizedExitCode] = await run([
- "export",
- "-s",
- info.id,
- "--sanitize",
- "--server",
- server.url.toString(),
- ])
- expect(sanitizedExitCode).toBe(0)
- expect(JSON.parse(sanitized)).toEqual(sanitizedTransfer)
- expect(sanitization).toEqual(["false", "true"])
- } finally {
- await server.stop(true)
- }
- }, 15_000)
- test("export reports an empty session list without a stack trace", async () => {
- const server = Bun.serve({
- port: 0,
- fetch(request) {
- const url = new URL(request.url)
- if (url.pathname === "/api/health") return health()
- if (url.pathname === "/api/location") {
- return Response.json({
- directory: "/project",
- project: { id: "global", directory: "/project", canonical: "/project" },
- })
- }
- if (url.pathname === "/api/session") return Response.json({ data: [], cursor: {} })
- return new Response("Not found", { status: 404 })
- },
- })
- try {
- const [stdout, stderr, exitCode] = await run(["export", "--server", server.url.toString()])
- expect(exitCode).toBe(0)
- expect(stdout).toBe("")
- expect(stderr).toBe(`No sessions found${os.EOL}`)
- } finally {
- await server.stop(true)
- }
- })
- test("interactive export writes a temporary JSON file", async () => {
- const output = await writeExport(transfer, info.id, false)
- const file = output.trim()
- try {
- expect(path.dirname(file)).toBe(os.tmpdir())
- expect(await Bun.file(file).json()).toEqual(transfer)
- } finally {
- await fs.rm(file, { force: true })
- }
- })
- test("import validates a file and sends it to the resolved location", async () => {
- const root = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-import-"))
- const file = path.join(root, "session.json")
- await fs.writeFile(file, JSON.stringify(transfer))
- let imported: unknown
- const server = Bun.serve({
- port: 0,
- async fetch(request) {
- const url = new URL(request.url)
- if (url.pathname === "/api/health") return health()
- if (url.pathname === "/api/location") {
- return Response.json({
- directory: root,
- project: { id: "global", directory: root, canonical: root },
- })
- }
- if (url.pathname === "/api/session/import") {
- imported = await request.json()
- return Response.json({ data: { ...info, location: { directory: root } } })
- }
- return new Response("Not found", { status: 404 })
- },
- })
- try {
- const [stdout, , exitCode] = await run(["import", file, "--directory", root, "--server", server.url.toString()])
- expect(exitCode).toBe(0)
- expect(stdout).toBe(`Imported session: ${info.id}${os.EOL}`)
- expect(imported).toEqual({ ...transfer, location: { directory: root } })
- } finally {
- await server.stop(true)
- await fs.rm(root, { recursive: true, force: true })
- }
- })
- test("import reports an existing session without a stack trace", async () => {
- const root = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-import-conflict-"))
- const file = path.join(root, "session.json")
- await fs.writeFile(file, JSON.stringify(transfer))
- const server = Bun.serve({
- port: 0,
- fetch(request) {
- const url = new URL(request.url)
- if (url.pathname === "/api/health") return health()
- if (url.pathname === "/api/location") {
- return Response.json({
- directory: root,
- project: { id: "global", directory: root, canonical: root },
- })
- }
- if (url.pathname === "/api/session/import") return new Response("Conflict", { status: 409 })
- return new Response("Not found", { status: 404 })
- },
- })
- try {
- const [stdout, stderr, exitCode] = await run(["import", file, "--server", server.url.toString()])
- expect(exitCode).toBe(0)
- expect(stdout).toBe("")
- expect(stderr).toBe(`Session already exists${os.EOL}`)
- } finally {
- await server.stop(true)
- await fs.rm(root, { recursive: true, force: true })
- }
- })
|