fix(apiproxy): title-invalid only for the input's fault, presentable messages
The rename impl narrows on SessionTitleInvalidError: only an empty-normalizing title maps to title-invalid (its message renders verbatim in the rename dialog alert), while liveness/disposal races fall to internal; the absent-service message trims to one presentable sentence. rpc-schemas gains the title-invalid accept/missing-details lines; cosmetic ordering (type-only import comment, tsconfig reference, schema import order) restored.
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
* sessions.rename delegation through the composed SessionTitleService. The
|
||||
* agent factory is a structural stub whose createAgent forwards seed/meta into
|
||||
* the real SessionStore, and whose resume never runs (every source here is
|
||||
* already attached).
|
||||
* already attached). Cold-session resolution is the shared `agentFor` path —
|
||||
* api-proxy-cold.spec.ts owns the resume evidence for every unary that rides
|
||||
* it, rename included.
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
@@ -82,20 +84,37 @@ describe('sessions.rename', () => {
|
||||
expect(event?.data).toMatchObject({ title: 'new name', source: { kind: 'user' } })
|
||||
})
|
||||
|
||||
it('maps an empty-normalizing title to title-invalid', async () => {
|
||||
it('maps only an empty-normalizing title to title-invalid, with a presentable message', async () => {
|
||||
const ctx = await composed()
|
||||
const source = liveAgent(ctx, 'session-rename-bad', 1)
|
||||
|
||||
const response = await api(ctx).sessions.rename(request({ sessionId: source.id, title: ' ' }))
|
||||
// U+200B passes a client-side trim gate but normalizes to empty host-side.
|
||||
const response = await api(ctx).sessions.rename(request({ sessionId: source.id, title: ' ' }))
|
||||
expect(response.result.ok).toBe(false)
|
||||
if (!response.result.ok) {
|
||||
expect(response.result.error).toMatchObject({
|
||||
code: 'title-invalid',
|
||||
details: { sessionId: source.id },
|
||||
})
|
||||
// The message renders verbatim in the rename dialog's alert.
|
||||
expect(response.result.error.message).toBe('session title must contain visible characters')
|
||||
}
|
||||
})
|
||||
|
||||
it('maps a non-validation rename failure (stale session object) to internal, not title-invalid', async () => {
|
||||
const ctx = await composed()
|
||||
// The registered agent holds a session object from another store: the
|
||||
// title service's liveness check throws a plain Error, which must not
|
||||
// read as the user's fault.
|
||||
const foreign = await composed(false)
|
||||
const stale = liveAgent(foreign, 'session-rename-stale', 1)
|
||||
ctx.agents.register({ id: stale.id, session: stale, status: 'idle', ctx } as Agent)
|
||||
|
||||
const response = await api(ctx).sessions.rename(request({ sessionId: stale.id, title: 'name' }))
|
||||
expect(response.result.ok).toBe(false)
|
||||
if (!response.result.ok) expect(response.result.error.code).toBe('internal')
|
||||
})
|
||||
|
||||
it('answers internal when the composition mounts no session-title service', async () => {
|
||||
const ctx = await composed(false)
|
||||
const source = liveAgent(ctx, 'session-no-titles', 1)
|
||||
@@ -104,7 +123,7 @@ describe('sessions.rename', () => {
|
||||
expect(response.result.ok).toBe(false)
|
||||
if (!response.result.ok) {
|
||||
expect(response.result.error.code).toBe('internal')
|
||||
expect(response.result.error.message).toMatch(/session-title service is absent/)
|
||||
expect(response.result.error.message).toMatch(/mounts no session-title service/)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -70,11 +70,13 @@ describe('rpcErrorSchema', () => {
|
||||
expect(rpcErrorSchema.parse({ code: 'agent-busy', message: 'm', details: { reason: 'r' } }).code).toBe('agent-busy')
|
||||
expect(rpcErrorSchema.parse({ code: 'command-error', message: 'm', details: {} }).code).toBe('command-error')
|
||||
expect(rpcErrorSchema.parse({ code: 'unknown-command', message: 'm', details: {} }).code).toBe('unknown-command')
|
||||
expect(rpcErrorSchema.parse({ code: 'title-invalid', message: 'm', details: { sessionId: 's' } }).code).toBe('title-invalid')
|
||||
expect(rpcErrorSchema.parse({ code: 'internal', message: 'm', details: {} }).code).toBe('internal')
|
||||
})
|
||||
|
||||
it('rejects a known code with missing details', () => {
|
||||
expect(() => rpcErrorSchema.parse({ code: 'agent-busy', message: 'm', details: {} })).toThrow()
|
||||
expect(() => rpcErrorSchema.parse({ code: 'title-invalid', message: 'm', details: {} })).toThrow()
|
||||
expect(() => rpcErrorSchema.parse({ code: 'command-error', message: 'm' })).toThrow()
|
||||
expect(() => rpcErrorSchema.parse({ code: 'nope', message: 'm', details: {} })).toThrow()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user