fix
This commit is contained in:
@@ -415,25 +415,25 @@ func TestV1OpenAPIYAMLProcessDualIDsAndGatesDocumentsContracts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestV1OpenAPIYAMLLegacyProcessItemEprelShape locks LegacyProcessItem.eprel
|
||||
// TestV1OpenAPIYAMLProcessItemEprelShape locks ProcessItem.eprel
|
||||
// to the live nested object from eprel.MergeInto / extractEPRELFromAttrs.
|
||||
func TestV1OpenAPIYAMLLegacyProcessItemEprelShape(t *testing.T) {
|
||||
func TestV1OpenAPIYAMLProcessItemEprelShape(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
root := mustParseOpenAPIRoot(t, v1OpenAPIYAML)
|
||||
comps, _ := root["components"].(map[string]any)
|
||||
schemas, _ := comps["schemas"].(map[string]any)
|
||||
item, _ := schemas["LegacyProcessItem"].(map[string]any)
|
||||
item, _ := schemas["ProcessItem"].(map[string]any)
|
||||
itemProps, _ := item["properties"].(map[string]any)
|
||||
eprel, _ := itemProps["eprel"].(map[string]any)
|
||||
eprelProps, _ := eprel["properties"].(map[string]any)
|
||||
if eprelProps == nil {
|
||||
t.Fatal("LegacyProcessItem.eprel.properties missing")
|
||||
t.Fatal("ProcessItem.eprel.properties missing")
|
||||
}
|
||||
want := []string{"id", "label", "pdf", "energy_class", "energy_scale"}
|
||||
for _, key := range want {
|
||||
if _, ok := eprelProps[key]; !ok {
|
||||
t.Fatalf("LegacyProcessItem.eprel missing property %q (live shape)", key)
|
||||
t.Fatalf("ProcessItem.eprel missing property %q (live shape)", key)
|
||||
}
|
||||
}
|
||||
if len(eprelProps) != len(want) {
|
||||
@@ -441,7 +441,161 @@ func TestV1OpenAPIYAMLLegacyProcessItemEprelShape(t *testing.T) {
|
||||
for k := range eprelProps {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
t.Fatalf("LegacyProcessItem.eprel properties = %v, want exactly %v", keys, want)
|
||||
t.Fatalf("ProcessItem.eprel properties = %v, want exactly %v", keys, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestV1OpenAPIYAMLNoLegacyWording(t *testing.T) {
|
||||
t.Parallel()
|
||||
assertOpenAPIYAMLOmitsToken(t, "legacy")
|
||||
}
|
||||
|
||||
func TestV1OpenAPIYAMLNoA1Wording(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Case-sensitive: lowercase "a1" appears in UUIDs and must stay allowed.
|
||||
assertOpenAPIYAMLOmitsToken(t, "A1")
|
||||
}
|
||||
|
||||
func TestV1OpenAPIYAMLOmitsInternalPromptWording(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, token := range []string{
|
||||
"role-section prompts",
|
||||
"plain description, meta",
|
||||
"after AI/manual edit",
|
||||
} {
|
||||
assertOpenAPIYAMLOmitsToken(t, token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestV1OpenAPIYAMLProcessItemCompletedExamplesOmitLeakyShape(t *testing.T) {
|
||||
t.Parallel()
|
||||
root := mustParseOpenAPIRoot(t, v1OpenAPIYAML)
|
||||
leaky := []string{"id", "title", "meta_title", "meta_description"}
|
||||
assertProcessItems := func(where string, items []any) {
|
||||
t.Helper()
|
||||
if len(items) == 0 {
|
||||
t.Fatalf("%s: expected completed ProcessItem example", where)
|
||||
}
|
||||
for i, raw := range items {
|
||||
item, _ := raw.(map[string]any)
|
||||
if item == nil {
|
||||
t.Fatalf("%s[%d]: not an object", where, i)
|
||||
}
|
||||
if _, ok := item["ean"]; !ok {
|
||||
t.Fatalf("%s[%d]: missing ean", where, i)
|
||||
}
|
||||
for _, k := range leaky {
|
||||
if _, ok := item[k]; ok {
|
||||
t.Fatalf("%s[%d] must omit %q (do not teach leaky ProcessItem shape)", where, i, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
comps, _ := root["components"].(map[string]any)
|
||||
examples, _ := comps["examples"].(map[string]any)
|
||||
completed, _ := examples["ProcessCompletedExample"].(map[string]any)
|
||||
val, _ := completed["value"].(map[string]any)
|
||||
data, _ := val["data"].(map[string]any)
|
||||
items, _ := data["items"].([]any)
|
||||
assertProcessItems("ProcessCompletedExample", items)
|
||||
|
||||
paths, _ := root["paths"].(map[string]any)
|
||||
productsProcess := openAPIJSONExamples(t, paths, "/products/process/{id}", "get", "200")
|
||||
prodCompleted, _ := productsProcess["completed"].(map[string]any)
|
||||
prodVal, _ := prodCompleted["value"].(map[string]any)
|
||||
prodData, _ := prodVal["data"].(map[string]any)
|
||||
prodItems, _ := prodData["items"].([]any)
|
||||
assertProcessItems("GET /products/process/{id} completed", prodItems)
|
||||
|
||||
legacyProcess := openAPIJSONExamples(t, paths, "/process/{id}", "get", "200")
|
||||
legacyCompleted, _ := legacyProcess["completed"].(map[string]any)
|
||||
legacyVal, _ := legacyCompleted["value"].(map[string]any)
|
||||
legacyItems, _ := legacyVal["items"].([]any)
|
||||
assertProcessItems("GET /process/{id} completed", legacyItems)
|
||||
}
|
||||
|
||||
func openAPIJSONExamples(t *testing.T, paths map[string]any, path, method, status string) map[string]any {
|
||||
t.Helper()
|
||||
p, _ := paths[path].(map[string]any)
|
||||
op, _ := p[method].(map[string]any)
|
||||
resps, _ := op["responses"].(map[string]any)
|
||||
resp, _ := resps[status].(map[string]any)
|
||||
content, _ := resp["content"].(map[string]any)
|
||||
appJSON, _ := content["application/json"].(map[string]any)
|
||||
examples, _ := appJSON["examples"].(map[string]any)
|
||||
if examples == nil {
|
||||
t.Fatalf("%s %s %s: missing application/json examples", method, path, status)
|
||||
}
|
||||
return examples
|
||||
}
|
||||
|
||||
func assertOpenAPIYAMLOmitsToken(t *testing.T, token string) {
|
||||
t.Helper()
|
||||
doc := string(v1OpenAPIYAML)
|
||||
if token == "legacy" {
|
||||
doc = strings.ToLower(doc)
|
||||
}
|
||||
if i := strings.Index(doc, token); i >= 0 {
|
||||
start := i - 40
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
end := i + 40
|
||||
if end > len(doc) {
|
||||
end = len(doc)
|
||||
}
|
||||
t.Fatalf("public OpenAPI YAML must not mention %q (near %q)", token, doc[start:end])
|
||||
}
|
||||
}
|
||||
|
||||
func TestV1OpenAPIYAMLEmptySecurityOnlyOnPublicProbes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
root := mustParseOpenAPIRoot(t, v1OpenAPIYAML)
|
||||
sec, _ := root["security"].([]any)
|
||||
if len(sec) == 0 {
|
||||
t.Fatal("document-level security must require API key schemes")
|
||||
}
|
||||
var hasBearer, hasAPIKey bool
|
||||
for _, item := range sec {
|
||||
m, _ := item.(map[string]any)
|
||||
if _, ok := m["BearerAuth"]; ok {
|
||||
hasBearer = true
|
||||
}
|
||||
if _, ok := m["ApiKeyAuth"]; ok {
|
||||
hasAPIKey = true
|
||||
}
|
||||
}
|
||||
if !hasBearer || !hasAPIKey {
|
||||
t.Fatal("document-level security must include BearerAuth and ApiKeyAuth")
|
||||
}
|
||||
|
||||
paths, _ := root["paths"].(map[string]any)
|
||||
methods := []string{"get", "post", "put", "patch", "delete"}
|
||||
var bad []string
|
||||
for p, raw := range paths {
|
||||
item, _ := raw.(map[string]any)
|
||||
for _, m := range methods {
|
||||
op, _ := item[m].(map[string]any)
|
||||
if op == nil {
|
||||
continue
|
||||
}
|
||||
rawSec, ok := op["security"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
arr, _ := rawSec.([]any)
|
||||
if len(arr) != 0 {
|
||||
continue
|
||||
}
|
||||
if p != "/health" && p != "/openapi.yaml" {
|
||||
bad = append(bad, m+" "+p)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(bad) > 0 {
|
||||
t.Fatalf("empty security (no API key) on non-probe operations: %v", bad)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user