import { describe, it, expect } from 'vitest' import { loadLib } from './helpers/loadLib.js' const { normalizeApiUrl, webRootFromApiUrl } = loadLib('url.js', [ 'normalizeApiUrl', 'webRootFromApiUrl' ]) describe('normalizeApiUrl', () => { // The bug this exists for (issue #2393): the instance root was accepted and // stored verbatim, so every request went to /credentials instead of // /api/credentials. That path is a Vue router route, so the SPA catch-all // answered GET with 200 HTML and rejected POST with 405 — which read as a // backend bug rather than a URL one. it('appends /api to an instance root', () => { expect(normalizeApiUrl('http://curator.traefik.internal')).toBe( 'http://curator.traefik.internal/api' ) }) it('leaves an API root alone rather than doubling the suffix', () => { expect(normalizeApiUrl('http://curator.traefik.internal/api')).toBe( 'http://curator.traefik.internal/api' ) }) it('is idempotent', () => { const once = normalizeApiUrl('http://curator.example.com') expect(normalizeApiUrl(once)).toBe(once) }) it('strips trailing slashes before deciding', () => { expect(normalizeApiUrl('http://curator.example.com/')).toBe('http://curator.example.com/api') expect(normalizeApiUrl('http://curator.example.com///')).toBe('http://curator.example.com/api') expect(normalizeApiUrl('http://curator.example.com/api/')).toBe('http://curator.example.com/api') }) it('trims surrounding whitespace (paste artifacts)', () => { expect(normalizeApiUrl(' http://curator.example.com ')).toBe( 'http://curator.example.com/api' ) }) it('matches the /api suffix case-insensitively', () => { expect(normalizeApiUrl('http://curator.example.com/API')).toBe('http://curator.example.com/API') }) it('returns empty string for empty/nullish input, never a bare "/api"', () => { // isConfigured() gates on truthiness, so a bogus '/api' here would read as // "configured" and produce a request against the options page's own origin. expect(normalizeApiUrl('')).toBe('') expect(normalizeApiUrl(' ')).toBe('') expect(normalizeApiUrl(null)).toBe('') expect(normalizeApiUrl(undefined)).toBe('') }) it('does not treat a path merely containing "api" as the suffix', () => { expect(normalizeApiUrl('http://curator.example.com/apiary')).toBe( 'http://curator.example.com/apiary/api' ) }) it('preserves a subpath deployment', () => { expect(normalizeApiUrl('http://host.internal/curator')).toBe('http://host.internal/curator/api') }) }) describe('webRootFromApiUrl', () => { // The SPA root, where the Vue router and the served XPI live. Used by // OPEN_ARTIST_PAGE and the self-update check — NOT the JSON API. it('strips the /api suffix', () => { expect(webRootFromApiUrl('http://curator.example.com/api')).toBe('http://curator.example.com') }) it('accepts an instance root unchanged', () => { expect(webRootFromApiUrl('http://curator.example.com')).toBe('http://curator.example.com') }) it('agrees with normalizeApiUrl in both directions', () => { for (const input of ['http://curator.example.com', 'http://curator.example.com/api']) { expect(normalizeApiUrl(webRootFromApiUrl(input))).toBe(normalizeApiUrl(input)) } }) it('preserves a subpath deployment', () => { expect(webRootFromApiUrl('http://host.internal/curator/api')).toBe('http://host.internal/curator') }) it('returns empty string for empty/nullish input', () => { expect(webRootFromApiUrl('')).toBe('') expect(webRootFromApiUrl(null)).toBe('') }) })