Task 02 (the whole runner) ended at 21:08 with no file and no commit: Ornith wrote the
implementation out in its reply (~700 lines), OpenCode compacted the session, and the model
then emitted a malformed tool call. It had also looked for the given test at a wrong path
("_files/…" relative to the plan: my ambiguity).
- 02-runner → 02-runner-core (types, Submit, Status, Touch, Cancel; no goroutines) and
03-runner-workers; the given test split to match (runner_core_test.go gains three tests:
queued storage, cancel while queued, Status never moves the lease); later tasks renumbered
04-09 and every cross-reference checked.
- Copy paths spelled from the repo root.
- guard.PreCheck (+ its test) is committed here by the owner: the old task 05 told the
implementer to edit internal/guard/, which AGENTS.md forbids. My contradiction.
- AGENTS.md rule 6: write code into files early, in small steps.
Both runner test files compile against a panic-only skeleton (the core file also alone) and
fail there on panic: todo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
3.2 KiB
Go
95 lines
3.2 KiB
Go
package api
|
|
|
|
// P6 task 05: v1fetch's per-target pipeline is extracted into s.fetchTarget, which the batch
|
|
// runner also calls. These tests pin that the two agree; every existing v1fetch test must also
|
|
// pass unchanged.
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/netip"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.wntrmute.dev/kyle/sift/internal/auth"
|
|
"git.wntrmute.dev/kyle/sift/internal/batch"
|
|
"git.wntrmute.dev/kyle/sift/internal/fetch"
|
|
"git.wntrmute.dev/kyle/sift/internal/guard"
|
|
)
|
|
|
|
// newTargetServer is setupAPI with loopback allowed; its key belongs to caller "test".
|
|
func newTargetServer(t *testing.T) (*Server, string) {
|
|
t.Helper()
|
|
return setupAPI(t, &guard.Guard{AllowPrefixes: []netip.Prefix{netip.MustParsePrefix("127.0.0.0/8")}})
|
|
}
|
|
|
|
func TestFetchTargetMatchesV1Fetch(t *testing.T) {
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
_, _ = w.Write([]byte(`<html><head><title>Same</title></head><body><article><h1>Same</h1><p>one pipeline, two callers</p></article></body></html>`))
|
|
}))
|
|
defer ts.Close()
|
|
s, key := newTargetServer(t)
|
|
c := &auth.Caller{Name: "test", Workspace: "hermes-fleet"}
|
|
|
|
res, err := s.fetchTarget(context.Background(), c, batch.Target{URL: ts.URL})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(res.Content, "one pipeline, two callers") {
|
|
t.Fatalf("content %q", res.Content)
|
|
}
|
|
if _, has := res.Meta["content"]; has {
|
|
t.Fatal("Meta must not repeat the content")
|
|
}
|
|
for _, k := range []string{"mode", "untrusted", "source", "warnings", "sizes", "timings"} {
|
|
if _, has := res.Meta[k]; !has {
|
|
t.Errorf("Meta lacks %q (it carries every envelope field except content)", k)
|
|
}
|
|
}
|
|
if res.Meta["mode"] != "clean" || res.Meta["untrusted"] != true {
|
|
t.Fatalf("meta %v", res.Meta)
|
|
}
|
|
|
|
// The same URL through /v1/fetch: identical content and mode, and the envelope has exactly
|
|
// the fields of Meta plus content.
|
|
rec := postJSON(t, s, "/v1/fetch", key, map[string]any{"url": ts.URL})
|
|
if rec.Code != 200 {
|
|
t.Fatalf("v1fetch %d %s", rec.Code, rec.Body.String())
|
|
}
|
|
var env map[string]any
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &env); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if env["content"] != res.Content || env["mode"] != res.Meta["mode"] {
|
|
t.Fatalf("v1fetch and fetchTarget disagree:\n%v\n%+v", env, res)
|
|
}
|
|
if len(env) != len(res.Meta)+1 {
|
|
t.Fatalf("envelope keys %d, Meta keys %d: want Meta + content", len(env), len(res.Meta))
|
|
}
|
|
}
|
|
|
|
func TestFetchTargetErrors(t *testing.T) {
|
|
s, _ := newTargetServer(t)
|
|
c := &auth.Caller{Name: "test", Workspace: "hermes-fleet"}
|
|
cases := []struct {
|
|
name string
|
|
target batch.Target
|
|
status int
|
|
msg string
|
|
}{
|
|
{"blocked", batch.Target{URL: "http://10.0.0.5/"}, 403, "blocked"},
|
|
{"bad mode", batch.Target{URL: "http://127.0.0.1/", Options: batch.Options{Mode: "shout"}}, 400, ""},
|
|
}
|
|
for _, tc := range cases {
|
|
_, err := s.fetchTarget(context.Background(), c, tc.target)
|
|
var fe *fetch.Error
|
|
if !errors.As(err, &fe) || fe.Status != tc.status || !strings.Contains(fe.Msg, tc.msg) {
|
|
t.Errorf("%s: err %v, want a *fetch.Error %d containing %q", tc.name, err, tc.status, tc.msg)
|
|
}
|
|
}
|
|
}
|