agent build
This commit is contained in:
@@ -222,11 +222,16 @@ func TestListEveryResource(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Assignments are empty by design in the source dataset. An empty collection is
|
||||
// 200 with an empty array, never a 404. api-contract.md §8.
|
||||
// An empty result is 200 with an empty array, never a 404. api-contract.md §8.
|
||||
//
|
||||
// Asked as a filter that matches nothing, rather than as a collection that
|
||||
// happens to be empty. This used to read /assignments on the strength of the
|
||||
// fixture shipping none, so seeding a single assignment broke a test about
|
||||
// status codes. The contract is about the empty result, not about which
|
||||
// collection is empty this week.
|
||||
func TestEmptyCollectionIs200(t *testing.T) {
|
||||
a := newAPI(t)
|
||||
r := a.do("GET", "/api/v1/assignments", nil)
|
||||
r := a.do("GET", "/api/v1/assignments?status=cancelled", nil)
|
||||
if r.code != http.StatusOK {
|
||||
t.Fatalf("code = %d, want 200", r.code)
|
||||
}
|
||||
@@ -473,12 +478,16 @@ func TestFilterEquality(t *testing.T) {
|
||||
// `Array.isArray(want) ? want.includes(got)`. api-contract.md §6.
|
||||
func TestFilterArrayMeansIN(t *testing.T) {
|
||||
a := newAPI(t)
|
||||
recs := a.do("GET", "/api/v1/job-applications?status=hired&status=interview", nil).records(t)
|
||||
// `assigned` is asked for deliberately: the fixture now carries one, and this
|
||||
// filter is literal — it matches the stored value, not the product's rule
|
||||
// that an assigned candidate also counts as hired.
|
||||
recs := a.do("GET",
|
||||
"/api/v1/job-applications?status=hired&status=interview&status=assigned", nil).records(t)
|
||||
if len(recs) != 8 {
|
||||
t.Errorf("hired+interview = %d, want 8 (3 hired, 5 interview)", len(recs))
|
||||
t.Errorf("hired+interview+assigned = %d, want 8 (2 hired, 5 interview, 1 assigned)", len(recs))
|
||||
}
|
||||
for _, r := range recs {
|
||||
if s := r["status"].(string); s != "hired" && s != "interview" {
|
||||
if s := r["status"].(string); s != "hired" && s != "interview" && s != "assigned" {
|
||||
t.Errorf("membership filter leaked status %q", s)
|
||||
}
|
||||
}
|
||||
@@ -1112,3 +1121,90 @@ func keysOf(m map[string]any) []string {
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// The build identifier has to be reachable, or "did my deploy land?" has no
|
||||
// answer. It was reported nowhere: the Dockerfile declared a VERSION arg,
|
||||
// compose passed it, and it reached no linker flag — so every deployment
|
||||
// described itself as nothing at all.
|
||||
//
|
||||
// Under /api/v1 rather than on /health on purpose: /health is public and
|
||||
// deliberately withholds its detail from the internet, and a build identifier
|
||||
// tells an unauthenticated reader exactly which source to go and read.
|
||||
func TestVersionEndpointReportsTheBuild(t *testing.T) {
|
||||
a := newAPI(t)
|
||||
|
||||
r := a.do("GET", "/api/v1/version", nil)
|
||||
if r.code != http.StatusOK {
|
||||
t.Fatalf("code = %d, want 200", r.code)
|
||||
}
|
||||
data, _ := r.body["data"].(map[string]any)
|
||||
if data == nil {
|
||||
t.Fatalf("no data envelope: %v", r.body)
|
||||
}
|
||||
if v, _ := data["version"].(string); v == "" {
|
||||
t.Errorf("version is empty; an unstamped build should still say \"dev\": %v", data)
|
||||
}
|
||||
if e, _ := data["env"].(string); e == "" {
|
||||
t.Errorf("env is empty: %v", data)
|
||||
}
|
||||
if n, _ := data["endpoints"].(float64); n < 1 {
|
||||
t.Errorf("endpoints = %v, want the served route count", data["endpoints"])
|
||||
}
|
||||
}
|
||||
|
||||
// It is behind the session like every other /api/v1 route.
|
||||
func TestVersionEndpointNeedsASession(t *testing.T) {
|
||||
a := newAPI(t)
|
||||
r := a.doAnon("GET", "/api/v1/version", nil)
|
||||
if r.code != http.StatusUnauthorized && r.code != http.StatusForbidden {
|
||||
t.Errorf("anonymous GET /api/v1/version = %d, want 401/403", r.code)
|
||||
}
|
||||
}
|
||||
|
||||
// An agent author picks capabilities from the real tool set, not a copy of it
|
||||
// kept in the frontend. A second list would drift, and the failure is silent:
|
||||
// the author picks a tool that no longer exists and gets an agent that quietly
|
||||
// cannot do the thing they picked.
|
||||
func TestToolsCatalogueIsServed(t *testing.T) {
|
||||
a := newAPI(t)
|
||||
|
||||
r := a.do("GET", "/api/v1/tools", nil)
|
||||
if r.code != http.StatusOK {
|
||||
t.Fatalf("code = %d, want 200", r.code)
|
||||
}
|
||||
list, _ := r.body["data"].([]any)
|
||||
if len(list) == 0 {
|
||||
t.Fatalf("no tools served: %v", r.body)
|
||||
}
|
||||
|
||||
seenWrite := false
|
||||
for _, raw := range list {
|
||||
tool, _ := raw.(map[string]any)
|
||||
name, _ := tool["name"].(string)
|
||||
desc, _ := tool["description"].(string)
|
||||
effect, _ := tool["effect"].(string)
|
||||
if name == "" || desc == "" {
|
||||
t.Errorf("a tool has no name or description: %v", tool)
|
||||
}
|
||||
if effect != "read" && effect != "write" {
|
||||
t.Errorf("%s has effect %q, want read or write", name, effect)
|
||||
}
|
||||
if effect == "write" {
|
||||
seenWrite = true
|
||||
// An author must be able to see that this one proposes changes.
|
||||
if confirm, _ := tool["requiresConfirmation"].(bool); !confirm {
|
||||
t.Errorf("%s writes but does not report requiring confirmation", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !seenWrite {
|
||||
t.Error("no write tool in the catalogue; the effect distinction is untested")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToolsCatalogueNeedsASession(t *testing.T) {
|
||||
a := newAPI(t)
|
||||
if r := a.doAnon("GET", "/api/v1/tools", nil); r.code != http.StatusUnauthorized && r.code != http.StatusForbidden {
|
||||
t.Errorf("anonymous GET /api/v1/tools = %d, want 401/403", r.code)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user