|
|
@@ -104,7 +104,7 @@ const createLocalWorkspace = (input: { projectID: Project.Info["id"]; type: stri
|
|
|
(info) => Workspace.Service.use((svc) => svc.remove(info.id)).pipe(Effect.ignore),
|
|
|
)
|
|
|
|
|
|
-const insertLegacyAssistantMessage = (sessionID: SessionIDType) =>
|
|
|
+const insertLegacyAssistantMessage = (sessionID: SessionIDType, time = 1) =>
|
|
|
Effect.sync(() => {
|
|
|
const message = new SessionMessage.Assistant({
|
|
|
id: SessionMessage.ID.create(),
|
|
|
@@ -115,7 +115,7 @@ const insertLegacyAssistantMessage = (sessionID: SessionIDType) =>
|
|
|
providerID: ProviderV2.ID.make("provider"),
|
|
|
variant: ModelV2.VariantID.make("default"),
|
|
|
},
|
|
|
- time: { created: DateTime.makeUnsafe(1) },
|
|
|
+ time: { created: DateTime.makeUnsafe(time) },
|
|
|
content: [],
|
|
|
})
|
|
|
Database.use((db) =>
|
|
|
@@ -126,9 +126,9 @@ const insertLegacyAssistantMessage = (sessionID: SessionIDType) =>
|
|
|
id: message.id,
|
|
|
session_id: sessionID,
|
|
|
type: message.type,
|
|
|
- time_created: 1,
|
|
|
+ time_created: time,
|
|
|
data: {
|
|
|
- time: { created: 1 },
|
|
|
+ time: { created: time },
|
|
|
agent: message.agent,
|
|
|
model: message.model,
|
|
|
content: message.content,
|
|
|
@@ -333,6 +333,73 @@ describe("session HttpApi", () => {
|
|
|
{ git: true, config: { formatter: false, lsp: false } },
|
|
|
)
|
|
|
|
|
|
+ it.instance(
|
|
|
+ "returns v2 public request errors for cursor and workspace query failures",
|
|
|
+ () =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const test = yield* TestInstance
|
|
|
+ const headers = { "x-opencode-directory": test.directory }
|
|
|
+ const session = yield* createSession({ title: "v2 cursor" })
|
|
|
+ yield* insertLegacyAssistantMessage(session.id, 1)
|
|
|
+ yield* insertLegacyAssistantMessage(session.id, 2)
|
|
|
+
|
|
|
+ const sessionPage = yield* request(`/api/session?limit=1`, { headers })
|
|
|
+ const sessionCursor = (yield* json<{ cursor: { next?: string } }>(sessionPage)).cursor.next
|
|
|
+ expect(sessionCursor).toBeTruthy()
|
|
|
+
|
|
|
+ const cursorWithFilter = yield* request(`/api/session?cursor=${sessionCursor}&search=v2`, { headers })
|
|
|
+ expect(cursorWithFilter.status).toBe(400)
|
|
|
+ expect(yield* responseJson(cursorWithFilter)).toMatchObject({
|
|
|
+ _tag: "InvalidCursorError",
|
|
|
+ message: "Cursor cannot be combined with order or filters",
|
|
|
+ })
|
|
|
+
|
|
|
+ const invalidSessionCursor = yield* request(`/api/session?cursor=invalid`, { headers })
|
|
|
+ expect(invalidSessionCursor.status).toBe(400)
|
|
|
+ expect(yield* responseJson(invalidSessionCursor)).toMatchObject({
|
|
|
+ _tag: "InvalidCursorError",
|
|
|
+ message: "Invalid cursor",
|
|
|
+ })
|
|
|
+
|
|
|
+ const mismatchedRouting = yield* request(`/api/session?cursor=${sessionCursor}&directory=/elsewhere`, { headers })
|
|
|
+ expect(mismatchedRouting.status).toBe(400)
|
|
|
+ expect(yield* responseJson(mismatchedRouting)).toMatchObject({
|
|
|
+ _tag: "InvalidCursorError",
|
|
|
+ message: "Cursor does not match requested directory or workspace",
|
|
|
+ })
|
|
|
+
|
|
|
+ const invalidWorkspace = yield* request(`/api/session?workspace=bad`, { headers })
|
|
|
+ expect(invalidWorkspace.status).toBe(400)
|
|
|
+ expect(yield* responseJson(invalidWorkspace)).toMatchObject({
|
|
|
+ _tag: "InvalidRequestError",
|
|
|
+ message: "Invalid workspace query parameter",
|
|
|
+ field: "workspace",
|
|
|
+ })
|
|
|
+
|
|
|
+ const messagePage = yield* request(`/api/session/${session.id}/message?limit=1`, { headers })
|
|
|
+ const messageCursor = (yield* json<{ cursor: { next?: string } }>(messagePage)).cursor.next
|
|
|
+ expect(messageCursor).toBeTruthy()
|
|
|
+
|
|
|
+ const messageCursorWithOrder = yield* request(
|
|
|
+ `/api/session/${session.id}/message?cursor=${messageCursor}&order=asc`,
|
|
|
+ { headers },
|
|
|
+ )
|
|
|
+ expect(messageCursorWithOrder.status).toBe(400)
|
|
|
+ expect(yield* responseJson(messageCursorWithOrder)).toMatchObject({
|
|
|
+ _tag: "InvalidCursorError",
|
|
|
+ message: "Cursor cannot be combined with order",
|
|
|
+ })
|
|
|
+
|
|
|
+ const invalidMessageCursor = yield* request(`/api/session/${session.id}/message?cursor=invalid`, { headers })
|
|
|
+ expect(invalidMessageCursor.status).toBe(400)
|
|
|
+ expect(yield* responseJson(invalidMessageCursor)).toMatchObject({
|
|
|
+ _tag: "InvalidCursorError",
|
|
|
+ message: "Invalid cursor",
|
|
|
+ })
|
|
|
+ }),
|
|
|
+ { git: true, config: { formatter: false, lsp: false } },
|
|
|
+ )
|
|
|
+
|
|
|
it.instance(
|
|
|
"serves sessions with migrated summary diffs missing file details",
|
|
|
() =>
|