Add exact-image compatibility policy, experimental admission, paginated build history and shared reauthentication checks. Preserve uncertain Docker creation records, bound frontend resource waits and renew runtime sessions proactively. Lazy-load and split Monaco, add regression and opt-in live Docker fault tests, and record full race and real-browser acceptance results.
168 lines
5.5 KiB
Go
168 lines
5.5 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestBuildHistoryPaginationAndOnDemandLogs(t *testing.T) {
|
|
a, sid := imageTestAPI(t, func(w http.ResponseWriter, r *http.Request) { t.Errorf("unexpected Docker request: %s", r.URL) })
|
|
buildQueue.Lock()
|
|
err := a.ensureBuilds()
|
|
buildQueue.Unlock()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for n := 0; n < 125; n++ {
|
|
_, err := a.s.DB.Exec("INSERT INTO image_builds(id,version,status,logs,created_at) VALUES(?,?,?,?,?)", fmt.Sprintf("task-%03d", n), "1.2.3", "failed", "token=secret-value"+string(rune(10))+strings.Repeat("x", buildLogLimit), "2026-01-01")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
request := func(target, session, method string) *httptest.ResponseRecorder {
|
|
r := httptest.NewRequest(method, target, nil)
|
|
if session != "" {
|
|
r.AddCookie(&http.Cookie{Name: "dsh_session", Value: session})
|
|
addTestAccess(r, session)
|
|
}
|
|
w := httptest.NewRecorder()
|
|
if strings.HasPrefix(r.URL.Path, "/api/builds/") {
|
|
a.buildDetail(w, r)
|
|
} else {
|
|
a.builds(w, r)
|
|
}
|
|
return w
|
|
}
|
|
for _, tc := range []struct {
|
|
query string
|
|
count int
|
|
first, more string
|
|
}{
|
|
{"", 20, "task-124", "true"}, {"?limit=100", 100, "task-124", "true"},
|
|
{"?limit=20&offset=120", 5, "task-004", "false"}, {"?offset=125", 0, "", "false"},
|
|
{"?limit=20&offset=105", 20, "task-019", "false"}, {"?limit=20&offset=104", 20, "task-020", "true"},
|
|
} {
|
|
w := request("/api/builds"+tc.query, sid, "GET")
|
|
var records []buildRecord
|
|
if w.Code != 200 || json.Unmarshal(w.Body.Bytes(), &records) != nil || len(records) != tc.count {
|
|
t.Fatalf("%s: %d %s", tc.query, w.Code, w.Body.String())
|
|
}
|
|
if tc.count > 0 && records[0].ID != tc.first {
|
|
t.Fatal(records[0].ID)
|
|
}
|
|
if w.Header().Get("X-Has-More") != tc.more || strings.Contains(w.Body.String(), "logs") || strings.Contains(w.Body.String(), "secret-value") {
|
|
t.Fatal("unbounded or invalid summary response", w.Body.String())
|
|
}
|
|
}
|
|
for _, query := range []string{"?limit=0", "?limit=101", "?limit=-1", "?offset=-1", "?offset=x", "?limit=1&limit=2", "?offset=9999999999999999999999999"} {
|
|
if w := request("/api/builds"+query, sid, "GET"); w.Code != 400 {
|
|
t.Fatal(query, w.Code)
|
|
}
|
|
}
|
|
w := request("/api/builds/task-000", sid, "GET")
|
|
var b buildRecord
|
|
if w.Code != 200 || json.Unmarshal(w.Body.Bytes(), &b) != nil || b.ID != "task-000" || b.Logs == "" || len(b.Logs) > buildLogLimit || strings.Contains(b.Logs, "secret-value") {
|
|
t.Fatal(w.Code, w.Body.String())
|
|
}
|
|
for _, path := range []string{"/api/builds/missing", "/api/builds/", "/api/builds/task-000/extra"} {
|
|
if w := request(path, sid, "GET"); w.Code != 404 {
|
|
t.Fatal(path, w.Code)
|
|
}
|
|
}
|
|
if w := request("/api/builds/task-000", "", "GET"); w.Code != 401 {
|
|
t.Fatal(w.Code)
|
|
}
|
|
if w := request("/api/builds", "", "GET"); w.Code != 401 {
|
|
t.Fatal(w.Code)
|
|
}
|
|
if w := request("/api/builds/task-000", sid, "POST"); w.Code != 405 {
|
|
t.Fatal(w.Code)
|
|
}
|
|
}
|
|
|
|
func TestBuildDetailRouteAndAccess(t *testing.T) {
|
|
a, sid := imageTestAPI(t, func(w http.ResponseWriter, r *http.Request) { t.Error("unexpected Docker call") })
|
|
buildQueue.Lock()
|
|
err := a.ensureBuilds()
|
|
buildQueue.Unlock()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err = a.s.DB.Exec("INSERT INTO image_builds(id,version,status,created_at) VALUES('task','1.2.3','failed','now')"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, csrf, err := a.s.SessionUser(sid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, tc := range []struct {
|
|
path, method string
|
|
access bool
|
|
status int
|
|
}{
|
|
{"/api/builds/task", "GET", true, 200}, {"/api/builds/task", "GET", false, 401},
|
|
{"/api/builds/task", "POST", true, 405}, {"/api/builds/", "GET", true, 404},
|
|
{"/api/builds/task%2Fextra", "GET", true, 404}, {"/api/builds/task/extra", "GET", true, 404},
|
|
} {
|
|
r := httptest.NewRequest(tc.method, tc.path, nil)
|
|
r.AddCookie(&http.Cookie{Name: "dsh_session", Value: sid})
|
|
if tc.access {
|
|
addTestAccess(r, sid)
|
|
}
|
|
if tc.method != "GET" {
|
|
r.Header.Set("Origin", "http://example.com")
|
|
r.Header.Set("X-CSRF-Token", csrf)
|
|
}
|
|
w := httptest.NewRecorder()
|
|
a.Handler().ServeHTTP(w, r)
|
|
if w.Code != tc.status {
|
|
t.Fatalf("%s %s: %d %s", tc.method, tc.path, w.Code, w.Body.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildPageReconcilesOnlySelectedRecords(t *testing.T) {
|
|
unavailable := false
|
|
calls := 0
|
|
a, sid := imageTestAPI(t, func(w http.ResponseWriter, r *http.Request) {
|
|
calls++
|
|
if unavailable {
|
|
w.WriteHeader(500)
|
|
return
|
|
}
|
|
if !strings.HasPrefix(r.URL.Path, "/v1.47/images/") {
|
|
t.Errorf("unexpected request %s", r.URL)
|
|
}
|
|
w.WriteHeader(404)
|
|
})
|
|
seedVerifiedImage(t, a, "first-image", "1.2.3")
|
|
seedVerifiedImage(t, a, "second-image", "1.2.4")
|
|
if _, err := a.s.DB.Exec("UPDATE image_builds SET created_at=CASE version WHEN '1.2.3' THEN 'z' ELSE 'a' END"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
request := func(offset int) *httptest.ResponseRecorder {
|
|
r := httptest.NewRequest("GET", fmt.Sprintf("/api/builds?limit=1&offset=%d", offset), nil)
|
|
r.AddCookie(&http.Cookie{Name: "dsh_session", Value: sid})
|
|
addTestAccess(r, sid)
|
|
w := httptest.NewRecorder()
|
|
a.builds(w, r)
|
|
return w
|
|
}
|
|
w := request(0)
|
|
if w.Code != 200 || calls != 1 || !strings.Contains(w.Body.String(), "missing") {
|
|
t.Fatal(w.Code, calls, w.Body.String())
|
|
}
|
|
var state string
|
|
if err := a.s.DB.QueryRow("SELECT status FROM image_builds WHERE version='1.2.4'").Scan(&state); err != nil || state != "success" {
|
|
t.Fatal(state, err)
|
|
}
|
|
unavailable = true
|
|
if w = request(1); w.Code != 502 {
|
|
t.Fatal(w.Code, w.Body.String())
|
|
}
|
|
}
|