mirror of
https://github.com/coder/coder.git
synced 2026-10-16 14:48:56 +08:00
## Stack context This is the base of a 3-PR stack promoting the chat API from `/api/experimental` to `/api/v2`: server compatibility mounts (this PR), codersdk promotion (#28497), and frontend path updates (#28498). ## Summary Double-mount the stable chat and MCP handlers under `/api/v2` while retaining the existing experimental routes for the one-release compatibility window decided in CODAGT-921. CODAGT-922 tracks removing the compatibility mounts. The shared route builders preserve existing authentication and middleware behavior. Experiment-gated, debug, tombstone, and legacy default-organization model routes remain experimental-only. Signed file URLs, external OAuth callback URLs, and mixed-version replica relays also remain on the experimental prefix during the transition. Update Swagger and the generated API reference for the promoted routes, including the workspace lookup and a runnable raw-body chat file upload example. Retain internal endpoints outside the published reference, share chat-file rate limits across both prefixes, enable CORS for the v2 MCP routes, and cover dual mounts plus exclusions with compatibility tests. Remote dogfood UAT passed for the promoted chat, model, MCP, and file flows. > [!NOTE] > Xum acted on Mike's behalf in this pull request. <!-- xum-attribution: model=claude-fable-5 thinking=high -->
106 lines
3.0 KiB
Go
106 lines
3.0 KiB
Go
package coderdtest_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/v2/coderd/apidoc"
|
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
|
)
|
|
|
|
func TestEndpointsDocumented(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
swaggerComments, err := coderdtest.ParseSwaggerComments("..", "../workspaceconnwatcher")
|
|
require.NoError(t, err, "can't parse swagger comments")
|
|
require.NotEmpty(t, swaggerComments, "swagger comments must be present")
|
|
|
|
// Coder Tasks has no swagger annotations because it is withdrawn from the
|
|
// product, so verify against a deployment where its routes are not
|
|
// registered.
|
|
values := coderdtest.DeploymentValues(t)
|
|
values.EnableAITasks = false
|
|
|
|
_, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{DeploymentValues: values})
|
|
coderdtest.VerifySwaggerDefinitions(t, api.APIHandler, swaggerComments, coderdtest.WithSwaggerRoutePrefix("/api/v2"))
|
|
}
|
|
|
|
func TestChatModelPathParametersFormatted(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var swagger struct {
|
|
Paths map[string]map[string]struct {
|
|
Parameters []struct {
|
|
Name string `json:"name"`
|
|
In string `json:"in"`
|
|
Format string `json:"format"`
|
|
} `json:"parameters"`
|
|
} `json:"paths"`
|
|
}
|
|
require.NoError(t, json.Unmarshal([]byte(apidoc.SwaggerInfo.ReadDoc()), &swagger))
|
|
|
|
operations := swagger.Paths["/api/v2/organizations/{organization}/chats/models/{model}"]
|
|
for _, method := range []string{"get", "patch", "delete"} {
|
|
t.Run(method, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, parameter := range operations[method].Parameters {
|
|
if parameter.Name == "model" && parameter.In == "path" {
|
|
require.Equal(t, "uuid", parameter.Format)
|
|
return
|
|
}
|
|
}
|
|
require.Fail(t, "model path parameter not found")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSDKFieldsFormatted(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fileSet := token.NewFileSet()
|
|
nodes, err := parser.ParseDir(fileSet, "../../codersdk", nil, parser.ParseComments)
|
|
require.NoError(t, err, "parser.ParseDir failed")
|
|
|
|
for _, node := range nodes {
|
|
ast.Inspect(node, func(n ast.Node) bool {
|
|
typeSpec, ok := n.(*ast.TypeSpec)
|
|
if !ok {
|
|
return true
|
|
}
|
|
structureName := typeSpec.Name
|
|
|
|
structType, ok := typeSpec.Type.(*ast.StructType)
|
|
if !ok {
|
|
return true // not a structure
|
|
}
|
|
|
|
for _, field := range structType.Fields.List {
|
|
selectorExpr, ok := field.Type.(*ast.SelectorExpr)
|
|
if !ok {
|
|
continue // rather a basic, or primitive
|
|
}
|
|
|
|
if field.Tag == nil || !strings.Contains(field.Tag.Value, `json:"`) {
|
|
continue // not a JSON property
|
|
}
|
|
|
|
switch selectorExpr.Sel.Name {
|
|
case "UUID":
|
|
assert.Contains(t, field.Tag.Value, `format:"uuid"`, `Swagger formatting requires to annotate the field with - format:"uuid". Location: %s/%s`, structureName, field.Names)
|
|
case "Time":
|
|
assert.Contains(t, field.Tag.Value, `format:"date-time"`, `Swagger formatting requires to annotate the field with - format:"date-time". Location: %s/%s`, structureName, field.Names)
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
}
|