plan --check treated BotFather profile reassertion as drift, so a scheduled check could never go quiet. Reassertion is now its own kind; a changed spec digest is the drift signal. apply reasserts the profile, updates channel copy when the spec changes, and is tested against a fake Telegram surface. The adapter reads its chat id from resolved state and its token from OpenBao, never the operator session. IEI-2 records that an approved provisioning plan is not autonomous Daimon action. Assistant: grok Assistant-Session: 01a09d5a-92c1-7802-915b-92bee57fc9f0
126 lines
3.6 KiB
Go
126 lines
3.6 KiB
Go
package secrets
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func testStore(t *testing.T, h http.Handler) *Store {
|
|
t.Helper()
|
|
srv := httptest.NewServer(h)
|
|
t.Cleanup(srv.Close)
|
|
t.Setenv("BAO_ADDR", srv.URL)
|
|
t.Setenv("BAO_TOKEN", "test-token")
|
|
s, err := NewFromEnv("hall-of-helix")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// The salt rule. Rotating it silently invalidates every longitudinal comparison
|
|
// the interface has made, with no visible failure -- so the tool must have no
|
|
// path that replaces an existing one.
|
|
func TestRefForDoesNotNeedAStore(t *testing.T) {
|
|
t.Setenv("BAO_MOUNT", "")
|
|
t.Setenv("FLUID_BAO_PREFIX", "")
|
|
got := RefFor("hall-of-helix", KeyBotToken)
|
|
want := "bao:platform/workloads/infotech/fluid-telegram/hall-of-helix/bot-token"
|
|
if got != want {
|
|
t.Fatalf("RefFor = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestCreateIfAbsentNeverOverwrites(t *testing.T) {
|
|
var writes int
|
|
existing := map[string]string{"salt": "the-original"}
|
|
s := testStore(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodPost {
|
|
writes++
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
json.NewEncoder(w).Encode(map[string]any{"data": map[string]any{"data": existing}})
|
|
}))
|
|
|
|
created, err := s.CreateIfAbsent(context.Background(), KeyRedactionSalt,
|
|
map[string]string{"salt": "a-replacement"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if created {
|
|
t.Error("reported creating a salt that already existed")
|
|
}
|
|
if writes != 0 {
|
|
t.Errorf("wrote over an existing salt (%d writes)", writes)
|
|
}
|
|
}
|
|
|
|
func TestCreateIfAbsentWritesWhenMissing(t *testing.T) {
|
|
var got map[string]any
|
|
s := testStore(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodPost {
|
|
json.NewDecoder(r.Body).Decode(&got)
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
|
|
created, err := s.CreateIfAbsent(context.Background(), KeyRedactionSalt,
|
|
map[string]string{"salt": "fresh"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !created {
|
|
t.Fatal("did not create a salt when none existed")
|
|
}
|
|
if got["data"].(map[string]any)["salt"] != "fresh" {
|
|
t.Errorf("wrote %v", got)
|
|
}
|
|
}
|
|
|
|
// A missing path is an ordinary first-run state, not an error.
|
|
func TestGetMissingIsNotAnError(t *testing.T) {
|
|
s := testStore(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
_, found, err := s.Get(context.Background(), KeyBotToken)
|
|
if err != nil || found {
|
|
t.Fatalf("found=%v err=%v", found, err)
|
|
}
|
|
}
|
|
|
|
// Ref is printed in plans and errors, so it must name a location and never
|
|
// carry a value.
|
|
func TestRefIsSafeToPrint(t *testing.T) {
|
|
s := testStore(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"data": map[string]any{"data": map[string]string{"token": "123:SECRET"}}})
|
|
}))
|
|
ref := s.Ref(KeyBotToken)
|
|
if strings.Contains(ref, "SECRET") || strings.Contains(ref, "test-token") {
|
|
t.Fatalf("Ref leaked a secret: %q", ref)
|
|
}
|
|
if !strings.Contains(ref, "hall-of-helix") || !strings.Contains(ref, KeyBotToken) {
|
|
t.Errorf("Ref should locate the secret: %q", ref)
|
|
}
|
|
}
|
|
|
|
// Errors name the path that failed, never what was at it.
|
|
func TestErrorsDoNotCarryValues(t *testing.T) {
|
|
s := testStore(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
_, _, err := s.Get(context.Background(), KeyOperatorSession)
|
|
if err == nil {
|
|
t.Fatal("expected an error")
|
|
}
|
|
if strings.Contains(err.Error(), "test-token") {
|
|
t.Fatalf("error leaked the bao token: %v", err)
|
|
}
|
|
}
|