mavweb: extend test coverage to credentials, signal, dash, history, revert handlers
- New credentials_test.go: 4 test functions covering credentialStore (new, save, lookup, update, persistence across restarts). - Extended handlers_test.go: fakeCore now supports WriteFact, Presence, RecentFacts/Nudges/Notes, RevertFact. Added 7 new test functions: TestNoCache, TestHandleSignal (5 subtestcases covering method guard, nil core, unknown key, known key, write error), TestHandleDash (3), TestHandleHistory (3), TestHandleRevert (6), and ListToolsError 502. - Fixed history.html: Go html/template requires conditional class rendered as separate <tr> branches, not inline attribute.
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewCredentialStore(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("non-existent path returns empty store", func(t *testing.T) {
|
||||
cs, err := newCredentialStore(filepath.Join(t.TempDir(), "nonexistent.json"))
|
||||
if err != nil {
|
||||
t.Fatalf("newCredentialStore(nonexistent) = _, %v, want nil", err)
|
||||
}
|
||||
if cs == nil {
|
||||
t.Fatal("newCredentialStore returned nil store")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("valid JSON loads credentials", func(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "creds.json")
|
||||
creds := map[string]localCred{
|
||||
"cred1": {PublicKey: []byte("key1"), SignCount: 5},
|
||||
}
|
||||
data, _ := json.Marshal(creds)
|
||||
os.WriteFile(path, data, 0600)
|
||||
|
||||
cs, err := newCredentialStore(path)
|
||||
if err != nil {
|
||||
t.Fatalf("newCredentialStore(valid) = _, %v, want nil", err)
|
||||
}
|
||||
pk, sc, err := cs.Lookup("cred1")
|
||||
if err != nil {
|
||||
t.Fatalf("Lookup cred1: %v", err)
|
||||
}
|
||||
if string(pk) != "key1" {
|
||||
t.Errorf("publicKey = %q, want %q", string(pk), "key1")
|
||||
}
|
||||
if sc != 5 {
|
||||
t.Errorf("signCount = %d, want 5", sc)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid JSON returns parse error", func(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "bad.json")
|
||||
os.WriteFile(path, []byte("{invalid"), 0600)
|
||||
|
||||
_, err := newCredentialStore(path)
|
||||
if err == nil {
|
||||
t.Fatal("newCredentialStore(invalid JSON) = _, nil, want error")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty file returns empty store", func(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "empty.json")
|
||||
os.WriteFile(path, nil, 0600)
|
||||
|
||||
cs, err := newCredentialStore(path)
|
||||
if err != nil {
|
||||
t.Fatalf("newCredentialStore(empty) = _, %v, want nil", err)
|
||||
}
|
||||
_, _, err = cs.Lookup("anything")
|
||||
if err == nil {
|
||||
t.Fatal("Lookup anything on empty store: expected error")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCredentialStoreSaveAndLookup(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("save then lookup returns credential with signCount=0", func(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "creds.json")
|
||||
cs, err := newCredentialStore(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = cs.Save("mykey", []byte("pubkey-data"))
|
||||
if err != nil {
|
||||
t.Fatalf("Save: %v", err)
|
||||
}
|
||||
|
||||
pk, sc, err := cs.Lookup("mykey")
|
||||
if err != nil {
|
||||
t.Fatalf("Lookup mykey: %v", err)
|
||||
}
|
||||
if string(pk) != "pubkey-data" {
|
||||
t.Errorf("publicKey = %q, want %q", string(pk), "pubkey-data")
|
||||
}
|
||||
if sc != 0 {
|
||||
t.Errorf("signCount = %d, want 0", sc)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("save duplicate ID returns error", func(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "creds.json")
|
||||
cs, _ := newCredentialStore(path)
|
||||
cs.Save("dup", []byte("first"))
|
||||
err := cs.Save("dup", []byte("second"))
|
||||
if err == nil {
|
||||
t.Fatal("Save duplicate: expected error")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("lookup non-existent ID returns error", func(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "creds.json")
|
||||
cs, _ := newCredentialStore(path)
|
||||
_, _, err := cs.Lookup("nobody")
|
||||
if err == nil {
|
||||
t.Fatal("Lookup non-existent: expected error")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCredentialStoreUpdateSignCount(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("save, update, then lookup returns new count", func(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "creds.json")
|
||||
cs, _ := newCredentialStore(path)
|
||||
cs.Save("ctr", []byte("pk"))
|
||||
|
||||
err := cs.UpdateSignCount("ctr", 42)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateSignCount: %v", err)
|
||||
}
|
||||
|
||||
_, sc, err := cs.Lookup("ctr")
|
||||
if err != nil {
|
||||
t.Fatalf("Lookup after update: %v", err)
|
||||
}
|
||||
if sc != 42 {
|
||||
t.Errorf("signCount = %d, want 42", sc)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("update non-existent credential returns error", func(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "creds.json")
|
||||
cs, _ := newCredentialStore(path)
|
||||
err := cs.UpdateSignCount("ghost", 1)
|
||||
if err == nil {
|
||||
t.Fatal("UpdateSignCount non-existent: expected error")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCredentialStorePersistence(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("credential persists across store instances", func(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "persist.json")
|
||||
cs, _ := newCredentialStore(path)
|
||||
cs.Save("persist-key", []byte("persist-data"))
|
||||
|
||||
cs2, err := newCredentialStore(path)
|
||||
if err != nil {
|
||||
t.Fatalf("newCredentialStore(reopen): %v", err)
|
||||
}
|
||||
pk, _, err := cs2.Lookup("persist-key")
|
||||
if err != nil {
|
||||
t.Fatalf("Lookup persist-key after reopen: %v", err)
|
||||
}
|
||||
if string(pk) != "persist-data" {
|
||||
t.Errorf("publicKey = %q, want %q", string(pk), "persist-data")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("file deleted results in empty store", func(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "deleted.json")
|
||||
cs, _ := newCredentialStore(path)
|
||||
cs.Save("gone-key", []byte("gone-data"))
|
||||
|
||||
os.Remove(path)
|
||||
|
||||
cs2, err := newCredentialStore(path)
|
||||
if err != nil {
|
||||
t.Fatalf("newCredentialStore(after delete): %v", err)
|
||||
}
|
||||
_, _, err = cs2.Lookup("gone-key")
|
||||
if err == nil {
|
||||
t.Fatal("Lookup gone-key after file delete: expected error")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -33,6 +33,27 @@ type fakeCore struct {
|
||||
gotEnableCmd []string
|
||||
gotEnableDest bool
|
||||
gotDisable string
|
||||
|
||||
// for handleSignal tests
|
||||
writeLog []ipc.WriteFactReq
|
||||
writeErr error
|
||||
signalErr error
|
||||
|
||||
// for handleDash tests
|
||||
presence ipc.Presence
|
||||
facts []ipc.Fact
|
||||
nudges []ipc.Nudge
|
||||
notes []ipc.Note
|
||||
dashErr error
|
||||
|
||||
// for handleRevert tests
|
||||
revertKey string
|
||||
revertNewID int64
|
||||
revertErr error
|
||||
|
||||
// for handleHistory tests
|
||||
historyFacts []ipc.Fact
|
||||
historyErr error
|
||||
}
|
||||
|
||||
func (f *fakeCore) EnableTool(_ context.Context, name string, cmd []string, destructive bool, _ time.Time) error {
|
||||
@@ -57,6 +78,58 @@ func (f *fakeCore) ListTools(_ context.Context, status string) ([]ipc.Tool, erro
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fakeCore) WriteFact(_ context.Context, req ipc.WriteFactReq) (int64, error) {
|
||||
if f.writeErr != nil {
|
||||
return 0, f.writeErr
|
||||
}
|
||||
if f.writeLog == nil {
|
||||
f.writeLog = make([]ipc.WriteFactReq, 0)
|
||||
}
|
||||
f.writeLog = append(f.writeLog, req)
|
||||
return int64(len(f.writeLog)), nil
|
||||
}
|
||||
|
||||
func (f *fakeCore) Presence(_ context.Context) (ipc.Presence, error) {
|
||||
if f.dashErr != nil {
|
||||
return ipc.Presence{}, f.dashErr
|
||||
}
|
||||
return f.presence, nil
|
||||
}
|
||||
|
||||
func (f *fakeCore) RecentFacts(_ context.Context, _ int) ([]ipc.Fact, error) {
|
||||
if f.historyErr != nil {
|
||||
return nil, f.historyErr
|
||||
}
|
||||
if f.historyFacts != nil {
|
||||
return f.historyFacts, nil
|
||||
}
|
||||
if f.dashErr != nil {
|
||||
return nil, f.dashErr
|
||||
}
|
||||
return f.facts, nil
|
||||
}
|
||||
|
||||
func (f *fakeCore) RecentNudges(_ context.Context, _ int) ([]ipc.Nudge, error) {
|
||||
if f.dashErr != nil {
|
||||
return nil, f.dashErr
|
||||
}
|
||||
return f.nudges, nil
|
||||
}
|
||||
|
||||
func (f *fakeCore) RecentNotes(_ context.Context, _ int) ([]ipc.Note, error) {
|
||||
if f.dashErr != nil {
|
||||
return nil, f.dashErr
|
||||
}
|
||||
return f.notes, nil
|
||||
}
|
||||
|
||||
func (f *fakeCore) RevertFact(_ context.Context, _ string) (int64, error) {
|
||||
if f.revertErr != nil {
|
||||
return 0, f.revertErr
|
||||
}
|
||||
return f.revertNewID, nil
|
||||
}
|
||||
|
||||
// --- GET ---
|
||||
|
||||
func TestHandleTools_GET_RendersAndEscapes(t *testing.T) {
|
||||
@@ -291,3 +364,282 @@ func TestWebAuthn_Begin_ReturnsChallenge(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- middleware ---
|
||||
|
||||
func TestNoCache(t *testing.T) {
|
||||
t.Parallel()
|
||||
rr := httptest.NewRecorder()
|
||||
innerCalled := false
|
||||
noCache(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
innerCalled = true
|
||||
w.Write([]byte("ok"))
|
||||
})).ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/", nil))
|
||||
if ct := rr.Header().Get("Cache-Control"); ct != "no-cache, no-store, must-revalidate" {
|
||||
t.Errorf("Cache-Control = %q, want %q", ct, "no-cache, no-store, must-revalidate")
|
||||
}
|
||||
if !innerCalled {
|
||||
t.Error("inner handler was not called")
|
||||
}
|
||||
if rr.Body.String() != "ok" {
|
||||
t.Errorf("body = %q, want %q", rr.Body.String(), "ok")
|
||||
}
|
||||
}
|
||||
|
||||
// --- handleSignal ---
|
||||
|
||||
func TestHandleSignal(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("GET returns 405", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
handleSignal(rr, httptest.NewRequest(http.MethodGet, "/api/signal", nil), &fakeCore{})
|
||||
if rr.Code != http.StatusMethodNotAllowed {
|
||||
t.Errorf("status = %d, want 405", rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nil core returns 503", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
handleSignal(rr, httptest.NewRequest(http.MethodPost, "/api/signal", nil), nil)
|
||||
if rr.Code != http.StatusServiceUnavailable {
|
||||
t.Errorf("status = %d, want 503", rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unknown key returns 400", func(t *testing.T) {
|
||||
core := &fakeCore{}
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/signal?key=nonexistent", nil)
|
||||
handleSignal(rr, req, core)
|
||||
if rr.Code != http.StatusBadRequest {
|
||||
t.Errorf("status = %d, want 400", rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("known key desk_active writes fact with correct params", func(t *testing.T) {
|
||||
core := &fakeCore{}
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/signal?key=desk_active", nil)
|
||||
handleSignal(rr, req, core)
|
||||
if rr.Code != http.StatusNoContent {
|
||||
t.Fatalf("status = %d, want 204", rr.Code)
|
||||
}
|
||||
if len(core.writeLog) != 1 {
|
||||
t.Fatalf("writeLog calls = %d, want 1", len(core.writeLog))
|
||||
}
|
||||
reqF := core.writeLog[0]
|
||||
if reqF.Source != "infer:hyprland" {
|
||||
t.Errorf("source = %q, want %q", reqF.Source, "infer:hyprland")
|
||||
}
|
||||
if reqF.Value != `"active"` {
|
||||
t.Errorf("value = %q, want %q", reqF.Value, `"active"`)
|
||||
}
|
||||
if reqF.Kind != "env" {
|
||||
t.Errorf("kind = %q, want %q", reqF.Kind, "env")
|
||||
}
|
||||
if reqF.Confidence != 1.0 {
|
||||
t.Errorf("confidence = %f, want 1.0", reqF.Confidence)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("core WriteFact error returns 502", func(t *testing.T) {
|
||||
core := &fakeCore{writeErr: ipc.ErrForbidden}
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/signal?key=desk_active", nil)
|
||||
handleSignal(rr, req, core)
|
||||
if rr.Code != http.StatusBadGateway {
|
||||
t.Errorf("status = %d, want 502", rr.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// --- handleDash ---
|
||||
|
||||
func TestHandleDash(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("nil core returns 503", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
handleDash(rr, httptest.NewRequest(http.MethodGet, "/dash", nil), nil)
|
||||
if rr.Code != http.StatusServiceUnavailable {
|
||||
t.Errorf("status = %d, want 503", rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("core API error returns 502", func(t *testing.T) {
|
||||
core := &fakeCore{dashErr: ipc.ErrNoFact}
|
||||
rr := httptest.NewRecorder()
|
||||
handleDash(rr, httptest.NewRequest(http.MethodGet, "/dash", nil), core)
|
||||
if rr.Code != http.StatusBadGateway {
|
||||
t.Errorf("status = %d, want 502", rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("renders template with data", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
core := &fakeCore{
|
||||
presence: ipc.Presence{Bucket: ipc.Present, Score: 0.75, Updated: now},
|
||||
facts: []ipc.Fact{
|
||||
{Kind: "env", Key: "test-key", Value: `"val"`, Source: "test", Confidence: 1.0},
|
||||
},
|
||||
nudges: []ipc.Nudge{
|
||||
{Rule: "test-rule", Channel: "test-chan", Message: "hello", Outcome: "pending"},
|
||||
},
|
||||
notes: []ipc.Note{
|
||||
{Text: "a note", Source: "user"},
|
||||
},
|
||||
}
|
||||
rr := httptest.NewRecorder()
|
||||
handleDash(rr, httptest.NewRequest(http.MethodGet, "/dash", nil), core)
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200; body=%s", rr.Code, rr.Body.String())
|
||||
}
|
||||
body := rr.Body.String()
|
||||
if !strings.Contains(body, "present") {
|
||||
t.Error("rendered output missing presence bucket")
|
||||
}
|
||||
if !strings.Contains(body, "0.75") {
|
||||
t.Error("rendered output missing presence score")
|
||||
}
|
||||
if !strings.Contains(body, "test-key") {
|
||||
t.Error("rendered output missing fact key")
|
||||
}
|
||||
if !strings.Contains(body, "test-rule") {
|
||||
t.Error("rendered output missing nudge rule")
|
||||
}
|
||||
if !strings.Contains(body, "a note") {
|
||||
t.Error("rendered output missing note text")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// --- handleHistory ---
|
||||
|
||||
func TestHandleHistory(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("nil core returns 503", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
handleHistory(rr, httptest.NewRequest(http.MethodGet, "/history", nil), nil)
|
||||
if rr.Code != http.StatusServiceUnavailable {
|
||||
t.Errorf("status = %d, want 503", rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("core RecentFacts error returns 502", func(t *testing.T) {
|
||||
core := &fakeCore{historyErr: ipc.ErrNoFact}
|
||||
rr := httptest.NewRecorder()
|
||||
handleHistory(rr, httptest.NewRequest(http.MethodGet, "/history", nil), core)
|
||||
if rr.Code != http.StatusBadGateway {
|
||||
t.Errorf("status = %d, want 502", rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("renders template with facts", func(t *testing.T) {
|
||||
core := &fakeCore{
|
||||
historyFacts: []ipc.Fact{
|
||||
{Kind: "self", Key: "hist-key", Value: `"hist-val"`, Source: "test", Confidence: 0.5},
|
||||
},
|
||||
}
|
||||
rr := httptest.NewRecorder()
|
||||
handleHistory(rr, httptest.NewRequest(http.MethodGet, "/history", nil), core)
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200; body=%s", rr.Code, rr.Body.String())
|
||||
}
|
||||
body := rr.Body.String()
|
||||
if !strings.Contains(body, "hist-key") {
|
||||
t.Error("rendered output missing fact key")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// --- handleRevert ---
|
||||
|
||||
func TestHandleRevert(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("GET returns 405", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
handleRevert(rr, httptest.NewRequest(http.MethodGet, "/api/revert", nil), &fakeCore{})
|
||||
if rr.Code != http.StatusMethodNotAllowed {
|
||||
t.Errorf("status = %d, want 405", rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nil core returns 503", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
handleRevert(rr, httptest.NewRequest(http.MethodPost, "/api/revert", nil), nil)
|
||||
if rr.Code != http.StatusServiceUnavailable {
|
||||
t.Errorf("status = %d, want 503", rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty key returns 400", func(t *testing.T) {
|
||||
core := &fakeCore{}
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/revert", strings.NewReader("key="))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
handleRevert(rr, req, core)
|
||||
if rr.Code != http.StatusBadRequest {
|
||||
t.Errorf("status = %d, want 400", rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("core returns ErrNoFact returns 404", func(t *testing.T) {
|
||||
core := &fakeCore{revertErr: ipc.ErrNoFact}
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/revert", strings.NewReader("key=missing"))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
handleRevert(rr, req, core)
|
||||
if rr.Code != http.StatusNotFound {
|
||||
t.Errorf("status = %d, want 404", rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("core returns error returns 502", func(t *testing.T) {
|
||||
core := &fakeCore{revertErr: ipc.ErrForbidden}
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/revert", strings.NewReader("key=somekey"))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
handleRevert(rr, req, core)
|
||||
if rr.Code != http.StatusBadGateway {
|
||||
t.Errorf("status = %d, want 502", rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("happy path returns JSON with reverted and new_id", func(t *testing.T) {
|
||||
core := &fakeCore{revertNewID: 42}
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/revert", strings.NewReader("key=test-key"))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
handleRevert(rr, req, core)
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200", rr.Code)
|
||||
}
|
||||
if ct := rr.Header().Get("Content-Type"); ct != "application/json" {
|
||||
t.Errorf("Content-Type = %q, want application/json", ct)
|
||||
}
|
||||
body := rr.Body.String()
|
||||
if !strings.Contains(body, `"reverted":true`) {
|
||||
t.Errorf("body missing reverted:true: %s", body)
|
||||
}
|
||||
if !strings.Contains(body, `"new_id":42`) {
|
||||
t.Errorf("body missing new_id:42: %s", body)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// --- handleTools ListTools error ---
|
||||
|
||||
func TestHandleTools_ListToolsError_502(t *testing.T) {
|
||||
t.Parallel()
|
||||
core := &fakeCore{listErr: ipc.ErrForbidden}
|
||||
rr := httptest.NewRecorder()
|
||||
handleTools(rr, postForm("enable", url.Values{
|
||||
"name": {"svc"}, "cmd": {"systemctl restart"},
|
||||
}), core)
|
||||
if rr.Code != http.StatusBadGateway {
|
||||
t.Fatalf("status = %d, want 502; body=%s", rr.Code, rr.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<div id=msg></div>
|
||||
<table>
|
||||
<tr><th>when<th>kind<th>key<th>value<th>source<th>conf<th></tr>
|
||||
{{range .Facts}}<tr{{if .VoidsID}} class=voided{{end}}>
|
||||
{{range .Facts}}{{if .VoidsID}}<tr class=voided>{{else}}<tr>{{end}}
|
||||
<td>{{if .VoidsID}}<span class=void-badge>⨯ voided</span>{{end}}{{.Ts.Format "2006-01-02 15:04"}}</td>
|
||||
<td>{{.Kind}}</td>
|
||||
<td class=key>{{.Key}}</td>
|
||||
|
||||
Reference in New Issue
Block a user