Files
zeekay 40d120dbf7 refactor(api): say /v1/ in the Python callers
Every first-party Python caller and test now names the mount the API actually
answers on. Mechanical: URL literals only, and only where the /api/ was ours --
a preceding word character means it was a module path like insights/api/ or a
third-party host like slack.com/api/, and those are untouched.

Left saying /api/, each for a reason rather than for safety:
- products/warehouse_sources/.../sources/** is a catalogue of vendor endpoints
- services/llm-gateway deploys separately and is under a code freeze, so moving
  its two calls would break it during the skew
- the mcp_store and Slack/Teams redirect_uris are registered with third parties
  and must byte-match what was registered
- the Wizard and Raycast client-metadata paths are OAuth client_ids
- /api/surveys and /api/feature_flag/local_evaluation are SDK aliases the
  ingress routes to the Rust fleets, held by released SDK versions

Test URLconfs moved with the requests that call them, and the Datastore
user-selection fixtures now carry the leading slash that request.path has.
2026-08-08 18:38:44 -07:00

53 lines
2.0 KiB
TypeScript

import { parseAiBlobPointer, resolveAiBlobUrl, resolveDataUri } from './aiBlob'
const HASH = 'a'.repeat(64)
const POINTER = `phaiblob://v1/sha256/${HASH}?mime=image%2Fpng&size=131072`
describe('aiBlob', () => {
it('parses a pointer', () => {
expect(parseAiBlobPointer(POINTER)).toEqual({
version: 'v1',
algo: 'sha256',
hash: HASH,
mime: 'image/png',
size: 131072,
})
})
it('resolves a pointer to the environment endpoint', () => {
expect(resolveAiBlobUrl(POINTER, 1)).toBe(`/v1/projects/1/ai_blob/v1/sha256/${HASH}`)
})
it.each([
['http url', 'https://example.com/a.png'],
['data uri', 'data:image/png;base64,AAAA'],
['plain text', 'hello'],
['bad hash', 'phaiblob://v1/sha256/xyz?mime=a&size=1'],
['unknown algo', `phaiblob://v1/md5/${HASH}?mime=a&size=1`],
['old scheme', `phblob://v1/sha256/${HASH}?mime=a&size=1`],
])('passes through unchanged: %s', (_name, value) => {
expect(parseAiBlobPointer(value)).toBeNull()
expect(resolveAiBlobUrl(value, 1)).toBe(value)
})
it('passes through when teamId is missing', () => {
expect(resolveAiBlobUrl(POINTER, null)).toBe(POINTER)
})
it.each([
['image/png', `data:image/png;base64,${POINTER}`],
['application/pdf', `data:application/pdf;base64,${POINTER}`],
])('resolves a pointer that a recipe wrapped in a %s data uri', (_mime, wrapped) => {
expect(parseAiBlobPointer(wrapped)).toMatchObject({ hash: HASH })
expect(resolveAiBlobUrl(wrapped, 1)).toBe(`/v1/projects/1/ai_blob/v1/sha256/${HASH}`)
})
it('resolves a pointer data field to the blob endpoint, ignoring the passed mime type', () => {
expect(resolveDataUri(POINTER, 'image/png', 1)).toBe(`/v1/projects/1/ai_blob/v1/sha256/${HASH}`)
})
it('builds a data: URI from raw base64 when the data field is not a pointer', () => {
expect(resolveDataUri('AAAA', 'image/png', 1)).toBe('data:image/png;base64,AAAA')
})
})