package adapter import ( "context" "testing" ) // mockAdapter is a minimal in-memory PlatformAdapter used to prove the // interface is satisfiable and to exercise its surface. It mocks the contract, // not a concrete adapter type. type mockAdapter struct { name string cfg map[string]any caps Capabilities handler MessageHandler sent []UnifiedMessage } func (m *mockAdapter) Name() string { return m.name } func (m *mockAdapter) Config() map[string]any { return m.cfg } func (m *mockAdapter) Connect(context.Context) error { return nil } func (m *mockAdapter) Disconnect(context.Context) error { return nil } func (m *mockAdapter) IsConnected() bool { return true } func (m *mockAdapter) HealthCheck(context.Context) error { return nil } func (m *mockAdapter) ReadMessages(context.Context, func(UnifiedMessage)) {} func (m *mockAdapter) SendMessage(_ context.Context, resp UnifiedResponse) error { m.sent = resp.Messages return nil } func (m *mockAdapter) SendProactive(context.Context, UnifiedMessage) error { return nil } func (m *mockAdapter) SetMessageHandler(fn MessageHandler) { m.handler = fn } func (m *mockAdapter) Capabilities() Capabilities { return m.caps } func (m *mockAdapter) ListChannels(context.Context) ([]ChannelInfo, error) { return nil, nil } // Interface conformance is enforced at compile time. var _ PlatformAdapter = (*mockAdapter)(nil) func TestMockAdapterSatisfiesContract(t *testing.T) { t.Parallel() ad := &mockAdapter{ name: "telegram", cfg: map[string]any{"token": "t-1"}, caps: DefaultCapabilities(), } if got := ad.Name(); got != "telegram" { t.Errorf("Name() = %q, want telegram", got) } if got := ad.Config()["token"]; got != "t-1" { t.Errorf("Config() token = %v, want t-1", got) } if got := ad.IsConnected(); !got { t.Error("IsConnected() = false, want true") } if got := ad.Capabilities().MaxMessageLength; got != defaultMaxMessageLength { t.Errorf("Capabilities().MaxMessageLength = %d, want %d", got, defaultMaxMessageLength) } } func TestMockAdapterSendMessageRecords(t *testing.T) { t.Parallel() ad := &mockAdapter{} msg := UnifiedMessage{ChannelType: ChannelDirect, ContentType: ContentText, Content: "pong"} resp := UnifiedResponse{Messages: []UnifiedMessage{msg}, ReplyTo: "m-1", Hint: "ok"} if err := ad.SendMessage(context.Background(), resp); err != nil { t.Fatalf("SendMessage() error = %v", err) } if len(ad.sent) != 1 || ad.sent[0].Content != "pong" { t.Errorf("sent = %+v, want one message with content pong", ad.sent) } } func TestDecodeConfig(t *testing.T) { t.Parallel() type cfg struct { Token string Port int } raw := map[string]any{"token": "abc", "port": 8080} var c cfg if err := DecodeConfig(raw, &c); err != nil { t.Fatalf("DecodeConfig() error = %v", err) } if c.Token != "abc" || c.Port != 8080 { t.Errorf("DecodeConfig() = %+v, want {abc 8080}", c) } } func TestDecodeConfigNil(t *testing.T) { t.Parallel() // A nil config map is treated as empty (decodes to zero value), so a pending // account with no keys yet builds with defaults rather than erroring. var c struct{ Token string } if err := DecodeConfig(nil, &c); err != nil { t.Fatalf("DecodeConfig(nil, ...) error = %v, want nil (empty config)", err) } if c.Token != "" { t.Fatalf("Token = %q, want empty zero value", c.Token) } }