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
310 lines
9 KiB
Go
310 lines
9 KiB
Go
package apply
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/tegwick/fluid-telegram/internal/plan"
|
|
"github.com/tegwick/fluid-telegram/internal/secrets"
|
|
"github.com/tegwick/fluid-telegram/internal/spec"
|
|
"github.com/tegwick/fluid-telegram/internal/state"
|
|
tgc "github.com/tegwick/fluid-telegram/internal/tg"
|
|
)
|
|
|
|
type memStore struct {
|
|
data map[string]map[string]string
|
|
}
|
|
|
|
func (s *memStore) CreateIfAbsent(_ context.Context, key string, fields map[string]string) (bool, error) {
|
|
if s.data == nil {
|
|
s.data = map[string]map[string]string{}
|
|
}
|
|
if _, ok := s.data[key]; ok {
|
|
return false, nil
|
|
}
|
|
s.data[key] = fields
|
|
return true, nil
|
|
}
|
|
|
|
func (s *memStore) Put(_ context.Context, key string, fields map[string]string) error {
|
|
if s.data == nil {
|
|
s.data = map[string]map[string]string{}
|
|
}
|
|
s.data[key] = fields
|
|
return nil
|
|
}
|
|
|
|
func (s *memStore) Ref(key string) string { return "bao:test/" + key }
|
|
|
|
type fakeConv struct {
|
|
taken map[string]bool
|
|
profiles int
|
|
}
|
|
|
|
func (c *fakeConv) RegisterBot(_ context.Context, _, username string) (string, string, error) {
|
|
if c.taken[username] {
|
|
return "", "", fmt.Errorf("%w: @%s", tgc.ErrUsernameTaken, username)
|
|
}
|
|
return username, "123:token-for-" + username, nil
|
|
}
|
|
|
|
func (c *fakeConv) SetProfile(context.Context, string, string, string, string) error {
|
|
c.profiles++
|
|
return nil
|
|
}
|
|
func (c *fakeConv) SetAvatar(context.Context, string, string) error { return nil }
|
|
|
|
type fakeSurface struct {
|
|
conv *fakeConv
|
|
nextChat int64
|
|
created []string
|
|
claimed []string
|
|
promoted []string
|
|
titles map[int64]string
|
|
abouts map[int64]string
|
|
botID int64
|
|
}
|
|
|
|
func (f *fakeSurface) OpenBotFather(context.Context) (BotConversation, error) {
|
|
if f.conv == nil {
|
|
f.conv = &fakeConv{}
|
|
}
|
|
return f.conv, nil
|
|
}
|
|
|
|
func (f *fakeSurface) ResolveBot(_ context.Context, _ string) (int64, error) {
|
|
if f.botID == 0 {
|
|
f.botID = 42
|
|
}
|
|
return f.botID, nil
|
|
}
|
|
|
|
func (f *fakeSurface) CreateChannel(_ context.Context, title, about string) (int64, error) {
|
|
f.nextChat--
|
|
id := f.nextChat
|
|
f.created = append(f.created, title)
|
|
if f.titles == nil {
|
|
f.titles = map[int64]string{}
|
|
f.abouts = map[int64]string{}
|
|
}
|
|
f.titles[id] = title
|
|
f.abouts[id] = about
|
|
return id, nil
|
|
}
|
|
|
|
func (f *fakeSurface) ClaimUsername(_ context.Context, _ int64, username string) (bool, error) {
|
|
f.claimed = append(f.claimed, username)
|
|
return true, nil
|
|
}
|
|
|
|
func (f *fakeSurface) PromoteBot(_ context.Context, _ int64, botUsername string) error {
|
|
f.promoted = append(f.promoted, botUsername)
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeSurface) EditChannelTitle(_ context.Context, chatID int64, title string) error {
|
|
if f.titles == nil {
|
|
f.titles = map[int64]string{}
|
|
}
|
|
f.titles[chatID] = title
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeSurface) EditChannelAbout(_ context.Context, chatID int64, about string) error {
|
|
if f.abouts == nil {
|
|
f.abouts = map[int64]string{}
|
|
}
|
|
f.abouts[chatID] = about
|
|
return nil
|
|
}
|
|
|
|
func presence() *spec.Presence {
|
|
return &spec.Presence{
|
|
SchemaVersion: "0.1", Campaign: "hall-of-helix", Interface: "i",
|
|
Bot: spec.Bot{Name: "HelixForge", UsernamePreference: []string{"HelixForgeBot"},
|
|
About: "about", Description: "desc"},
|
|
Channels: map[string]spec.Channel{
|
|
spec.Test: {Title: "test-title", Description: "test-about", Visibility: spec.Private},
|
|
spec.Live: {Title: "live-title", Description: "live-about", Visibility: spec.Public,
|
|
UsernamePreference: []string{"hallofhelix"}},
|
|
},
|
|
}
|
|
}
|
|
|
|
func opts(t *testing.T, rs *state.Resolved, surf *fakeSurface, store *memStore) Options {
|
|
t.Helper()
|
|
if rs == nil {
|
|
rs = &state.Resolved{}
|
|
}
|
|
if surf == nil {
|
|
surf = &fakeSurface{}
|
|
}
|
|
if store == nil {
|
|
store = &memStore{}
|
|
}
|
|
return Options{
|
|
Spec: presence(), SpecDigest: "sha256:x", State: rs,
|
|
StatePath: filepath.Join(t.TempDir(), "resolved.yaml"),
|
|
Store: store, Surface: surf, Out: &bytes.Buffer{},
|
|
}
|
|
}
|
|
|
|
func TestApplyRefusesBlockedPlan(t *testing.T) {
|
|
p := &plan.Plan{Actions: []plan.Action{{Kind: plan.Block, Target: "bot", Detail: "gone"}}}
|
|
err := Run(context.Background(), p, opts(t, nil, nil, nil))
|
|
if err == nil {
|
|
t.Fatal("applied a blocked plan")
|
|
}
|
|
}
|
|
|
|
func TestFirstRunCreatesBotAndTestChannelOnly(t *testing.T) {
|
|
surf := &fakeSurface{}
|
|
store := &memStore{}
|
|
o := opts(t, nil, surf, store)
|
|
p := &plan.Plan{Actions: []plan.Action{
|
|
{Kind: plan.Attempt, Target: "bot"},
|
|
{Kind: plan.Create, Target: "channel.test"},
|
|
{Kind: plan.Defer, Target: "channel.public"},
|
|
}}
|
|
if err := Run(context.Background(), p, o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if store.data[secrets.KeyBotToken]["token"] == "" {
|
|
t.Fatal("bot token was not stored")
|
|
}
|
|
if _, ok := store.data[secrets.KeyRedactionSalt]; !ok {
|
|
t.Fatal("redaction salt was not created")
|
|
}
|
|
if o.State.Bot.Username != "HelixForgeBot" || o.State.Bot.ID == 0 {
|
|
t.Fatalf("bot state = %+v", o.State.Bot)
|
|
}
|
|
if _, ok := o.State.Channels[spec.Test]; !ok {
|
|
t.Fatal("test channel was not recorded")
|
|
}
|
|
if _, ok := o.State.Channels[spec.Live]; ok {
|
|
t.Fatal("public channel was created before the test channel was verified")
|
|
}
|
|
if len(surf.promoted) != 1 {
|
|
t.Fatalf("promotions = %v", surf.promoted)
|
|
}
|
|
}
|
|
|
|
func TestWalksUsernameCandidates(t *testing.T) {
|
|
surf := &fakeSurface{conv: &fakeConv{taken: map[string]bool{"HelixForgeBot": true}}}
|
|
sp := presence()
|
|
sp.Bot.UsernamePreference = []string{"HelixForgeBot", "HelixForgePublishBot"}
|
|
o := opts(t, nil, surf, nil)
|
|
o.Spec = sp
|
|
p := &plan.Plan{Actions: []plan.Action{{Kind: plan.Attempt, Target: "bot"}}}
|
|
if err := Run(context.Background(), p, o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if o.State.Bot.Username != "HelixForgePublishBot" {
|
|
t.Fatalf("username = %s", o.State.Bot.Username)
|
|
}
|
|
}
|
|
|
|
func TestExistingBotReassertsProfile(t *testing.T) {
|
|
surf := &fakeSurface{}
|
|
now := time.Now()
|
|
rs := &state.Resolved{
|
|
Campaign: "hall-of-helix", SpecDigest: "sha256:x",
|
|
Bot: state.Bot{Username: "HelixForgeBot", ID: 42},
|
|
Channels: map[string]state.Channel{
|
|
spec.Test: {ChatID: -100, AdminRights: []string{spec.PostMessages}, TestPublicationAt: &now},
|
|
spec.Live: {ChatID: -200, Username: "hallofhelix", AdminRights: []string{spec.PostMessages}},
|
|
},
|
|
}
|
|
o := opts(t, rs, surf, &memStore{data: map[string]map[string]string{
|
|
secrets.KeyRedactionSalt: {"salt": "already"},
|
|
}})
|
|
p := &plan.Plan{Actions: []plan.Action{{Kind: plan.Reassert, Target: "bot.profile"}}}
|
|
if err := Run(context.Background(), p, o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if surf.conv == nil || surf.conv.profiles == 0 {
|
|
t.Fatal("existing bot did not reassert its profile")
|
|
}
|
|
if len(surf.created) != 0 {
|
|
t.Fatalf("recreated channels: %v", surf.created)
|
|
}
|
|
}
|
|
|
|
func TestSpecChangeUpdatesChannelCopy(t *testing.T) {
|
|
surf := &fakeSurface{}
|
|
now := time.Now()
|
|
rs := &state.Resolved{
|
|
Campaign: "hall-of-helix", SpecDigest: "sha256:old",
|
|
Bot: state.Bot{Username: "HelixForgeBot", ID: 42},
|
|
Channels: map[string]state.Channel{
|
|
spec.Test: {ChatID: -100, AdminRights: []string{spec.PostMessages}, TestPublicationAt: &now},
|
|
spec.Live: {ChatID: -200, Username: "hallofhelix", AdminRights: []string{spec.PostMessages}},
|
|
},
|
|
}
|
|
o := opts(t, rs, surf, &memStore{data: map[string]map[string]string{
|
|
secrets.KeyRedactionSalt: {"salt": "already"},
|
|
}})
|
|
p := &plan.Plan{Actions: []plan.Action{{Kind: plan.Update, Target: "spec"}}}
|
|
if err := Run(context.Background(), p, o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if surf.titles[-100] != "test-title" || surf.titles[-200] != "live-title" {
|
|
t.Fatalf("titles = %v", surf.titles)
|
|
}
|
|
}
|
|
|
|
func TestSaltIsCreatedOnce(t *testing.T) {
|
|
store := &memStore{}
|
|
p := &plan.Plan{Actions: []plan.Action{{Kind: plan.Attempt, Target: "bot"}}}
|
|
if err := Run(context.Background(), p, opts(t, nil, nil, store)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
first := store.data[secrets.KeyRedactionSalt]["salt"]
|
|
rs := &state.Resolved{
|
|
Campaign: "hall-of-helix",
|
|
Bot: state.Bot{Username: "HelixForgeBot", ID: 42},
|
|
Channels: map[string]state.Channel{
|
|
spec.Test: {ChatID: -100, AdminRights: []string{spec.PostMessages}},
|
|
},
|
|
}
|
|
if err := Run(context.Background(), p, opts(t, rs, nil, store)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if store.data[secrets.KeyRedactionSalt]["salt"] != first {
|
|
t.Fatal("redaction salt was rotated")
|
|
}
|
|
}
|
|
|
|
func TestPublicReleasedAfterTestPublication(t *testing.T) {
|
|
now := time.Now()
|
|
rs := &state.Resolved{
|
|
Campaign: "hall-of-helix",
|
|
Bot: state.Bot{Username: "HelixForgeBot", ID: 42},
|
|
Channels: map[string]state.Channel{
|
|
spec.Test: {ChatID: -100, AdminRights: []string{spec.PostMessages}, TestPublicationAt: &now},
|
|
},
|
|
}
|
|
surf := &fakeSurface{}
|
|
o := opts(t, rs, surf, &memStore{data: map[string]map[string]string{
|
|
secrets.KeyRedactionSalt: {"salt": "already"},
|
|
}})
|
|
p := &plan.Plan{Actions: []plan.Action{{Kind: plan.Create, Target: "channel.public"}}}
|
|
if err := Run(context.Background(), p, o); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
pub, ok := o.State.Channels[spec.Live]
|
|
if !ok {
|
|
t.Fatal("public channel was not created")
|
|
}
|
|
if pub.Username != "hallofhelix" {
|
|
t.Fatalf("username = %s", pub.Username)
|
|
}
|
|
if !strings.Contains(strings.Join(surf.promoted, ","), "HelixForgeBot") {
|
|
t.Fatalf("bot was not promoted on public: %v", surf.promoted)
|
|
}
|
|
}
|