feat(apispec): describe /api/v1/import; regenerate openapi + schema.ts

Add import tag, importOperations() (GET + POST /api/v1/import), and schema
name mappings (ImportStatusResponse, ImportRunResponse, ImportReport) to
build.go. Regenerate openapi.yaml and frontend/src/api/schema.ts. Route-spec
parity test passes. Restore the nil-svc-501 import controller test now that
the spec includes the operation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Harshit Singh Bhandari 2026-06-26 20:51:30 +05:30
parent c8a9f6b7fd
commit 9a0e3ccbb7
No known key found for this signature in database
4 changed files with 234 additions and 7 deletions

View File

@ -51,6 +51,55 @@ paths:
summary: Stream CDC events with durable replay
tags:
- events
/api/v1/import:
get:
operationId: getImportStatus
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/ImportStatusResponse'
description: OK
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
description: Internal Server Error
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
description: Not Implemented
summary: Check whether a legacy AO install is available to import
tags:
- import
post:
operationId: runImport
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/ImportRunResponse'
description: OK
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
description: Internal Server Error
"501":
content:
application/json:
schema:
$ref: '#/components/schemas/APIError'
description: Not Implemented
summary: Run the legacy AO project import through the daemon store
tags:
- import
/api/v1/notifications:
get:
operationId: listNotifications
@ -1600,6 +1649,40 @@ components:
required:
- harness
type: object
ImportReport:
properties:
dryRun:
type: boolean
notes:
items:
type: string
type: array
projectsImported:
type: integer
projectsSkipped:
type: integer
required:
- dryRun
- projectsImported
- projectsSkipped
type: object
ImportRunResponse:
properties:
report:
$ref: '#/components/schemas/ImportReport'
required:
- report
type: object
ImportStatusResponse:
properties:
available:
type: boolean
legacyRoot:
type: string
required:
- available
- legacyRoot
type: object
KillSessionResponse:
properties:
freed:
@ -2429,3 +2512,5 @@ tags:
name: notifications
- description: Server-sent CDC event stream with durable replay
name: events
- description: Legacy AO project import (availability probe and run)
name: import

View File

@ -67,6 +67,8 @@ func Build() ([]byte, error) {
"Durable dashboard notifications"),
*(&openapi31.Tag{Name: "events"}).WithDescription(
"Server-sent CDC event stream with durable replay"),
*(&openapi31.Tag{Name: "import"}).WithDescription(
"Legacy AO project import (availability probe and run)"),
}
for _, op := range operations() {
@ -186,6 +188,11 @@ var schemaNames = map[string]string{
"ControllersSubmitReviewInput": "SubmitReviewInput",
// domain review entities
"DomainReviewRun": "ReviewRun",
// httpd/controllers — import wire envelopes
"ControllersImportStatusResponse": "ImportStatusResponse",
"ControllersImportRunResponse": "ImportRunResponse",
// legacyimport report
"LegacyimportReport": "ImportReport",
// service/project entities + DTOs
"ProjectProject": "Project",
"ProjectSummary": "ProjectSummary",
@ -273,9 +280,35 @@ func operations() []operation {
ops = append(ops, prOperations()...)
ops = append(ops, reviewOperations()...)
ops = append(ops, notificationOperations()...)
ops = append(ops, importOperations()...)
return ops
}
// importOperations declares the 2 /import operations. Must stay 1:1 with
// the routes ImportController.Register mounts (enforced by the parity test).
func importOperations() []operation {
return []operation{
{
method: http.MethodGet, path: "/api/v1/import", id: "getImportStatus", tag: "import",
summary: "Check whether a legacy AO install is available to import",
resps: []respUnit{
{http.StatusOK, controllers.ImportStatusResponse{}},
{http.StatusInternalServerError, envelope.APIError{}},
{http.StatusNotImplemented, envelope.APIError{}},
},
},
{
method: http.MethodPost, path: "/api/v1/import", id: "runImport", tag: "import",
summary: "Run the legacy AO project import through the daemon store",
resps: []respUnit{
{http.StatusOK, controllers.ImportRunResponse{}},
{http.StatusInternalServerError, envelope.APIError{}},
{http.StatusNotImplemented, envelope.APIError{}},
},
},
}
}
func notificationOperations() []operation {
return []operation{
{

View File

@ -88,10 +88,12 @@ func TestImportAPI_RunError(t *testing.T) {
}
}
// TestImportAPI_NilSvcReturns501 verifies the controller falls through to the
// 501 stub when no import service is wired. This test only passes after
// Task 4 regenerates openapi.yaml to include /api/v1/import; until then the
// apispec.NotImplemented call panics on the missing operation.
// Kept as a reminder; re-enable after api:spec regen.
//
// func TestImportAPI_NilSvcReturns501(t *testing.T) { ... }
func TestImportAPI_NilSvcReturns501(t *testing.T) {
log := slog.New(slog.NewTextHandler(io.Discard, nil))
srv := httptest.NewServer(httpd.NewRouterWithControl(config.Config{}, log, nil, httpd.APIDeps{}, httpd.ControlDeps{}))
t.Cleanup(srv.Close)
body, status, _ := doRequest(t, srv, "GET", "/api/v1/import", "")
assertErrorCode(t, body, status, http.StatusNotImplemented, "NOT_IMPLEMENTED")
body, status, _ = doRequest(t, srv, "POST", "/api/v1/import", "")
assertErrorCode(t, body, status, http.StatusNotImplemented, "NOT_IMPLEMENTED")
}

View File

@ -21,6 +21,24 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/v1/import": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Check whether a legacy AO install is available to import */
get: operations["getImportStatus"];
put?: never;
/** Run the legacy AO project import through the daemon store */
post: operations["runImport"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/notifications": {
parameters: {
query?: never;
@ -551,6 +569,19 @@ export interface components {
DomainReviewerConfig: {
harness: string;
};
ImportReport: {
dryRun: boolean;
notes?: string[];
projectsImported: number;
projectsSkipped: number;
};
ImportRunResponse: {
report: components["schemas"]["ImportReport"];
};
ImportStatusResponse: {
available: boolean;
legacyRoot: string;
};
KillSessionResponse: {
freed?: boolean;
ok: boolean;
@ -918,6 +949,82 @@ export interface operations {
};
};
};
getImportStatus: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ImportStatusResponse"];
};
};
/** @description Internal Server Error */
500: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["APIError"];
};
};
/** @description Not Implemented */
501: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["APIError"];
};
};
};
};
runImport: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ImportRunResponse"];
};
};
/** @description Internal Server Error */
500: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["APIError"];
};
};
/** @description Not Implemented */
501: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["APIError"];
};
};
};
};
listNotifications: {
parameters: {
query?: {