Files
pad/internal/server
xarmian e621eacb9b feat(server): POST /api/v1/import/url endpoint + integration tests (TASK-1472) (#556)
Wires internal/urlimport into the API: Fetcher → Detect → converters.
Side-effect-free; the editor's "Insert from URL" modal owns any item
mutation (TASK-1474).

Endpoint:
- POST /api/v1/import/url, body {"url"}, response {markdown,
  detected_type, title?, source_url, fetched_at, content_type}.
- Status mapping: 400 invalid URL / SSRF / malformed body; 502 upstream
  failure (incl. size cap); 504 fetch timeout; 422 conversion failure.
- Swagger 2.0 fallback: detected as "openapi" by the sniffer but
  rejected by ConvertOpenAPI → falls through to ConvertGeneric and
  re-classifies the response as "generic" so the UI shows the right
  affordance.
- 30s wall-clock budget on the whole pipeline via context.WithTimeout
  (Fetcher's own timeout is the HTTP-level cap).

Package-level Fetcher is memoized via the existing sync.Once-cached
safe transport (shared keep-alive pool, no per-request leak). The
handler skips the pre-flight ValidateURL when the package fetcher
has AllowLocal=true so tests can swap in a loopback-friendly fetcher
without bypassing the production guard.

Integration tests (handlers_import_test.go):
- HTML happy path (httptest upstream, asserts detected_type/title/
  source_url/fetched_at/content_type all wired up).
- OpenAPI 3.x happy path (inline YAML upstream, asserts ConvertOpenAPI
  was invoked and detected_type=openapi).
- Swagger 2.0 fallback (asserts detected_type re-classified to
  generic, markdown non-empty).
- SSRF rejection (default fetcher, loopback URL → 400 mentioning
  "private"/"reserved").
- file:// scheme rejected (400).
- Missing/malformed body rejected (400).
- Upstream 5xx surfaces as 502.
- Size cap exceeded surfaces as 502.
- No-side-effects check: item count unchanged before/after import.

Parent: PLAN-1467.
2026-05-15 00:36:41 -04:00
..