mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
83b6ee608c
- Add SetupBotContainer to ContainerLifecycle interface so containers are automatically created when a bot is created, matching the existing cleanup-on-delete behavior. - Refactor schedule tools to use bot-scoped API paths and pass identity context for proper authorization. - Introduce dedicated trigger-schedule endpoint in chat resolver with explicit schedule payload instead of reusing the generic chat path. - Generate short-lived JWT tokens for schedule trigger callbacks with resolved bot owner identity. - Validate required parameters in NewLLMClient and NewOpenAIEmbedder constructors, returning errors instead of falling back to defaults. - Add unit tests for schedule token generation and chat resolver.
92 lines
2.0 KiB
Go
92 lines
2.0 KiB
Go
package schedule
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
)
|
|
|
|
type mockTriggerer struct {
|
|
called bool
|
|
botID string
|
|
payload TriggerPayload
|
|
token string
|
|
}
|
|
|
|
func (m *mockTriggerer) TriggerSchedule(_ context.Context, botID string, payload TriggerPayload, token string) error {
|
|
m.called = true
|
|
m.botID = botID
|
|
m.payload = payload
|
|
m.token = token
|
|
return nil
|
|
}
|
|
|
|
func TestGenerateTriggerToken(t *testing.T) {
|
|
secret := "test-secret-key-for-schedule"
|
|
svc := &Service{
|
|
jwtSecret: secret,
|
|
logger: slog.Default(),
|
|
}
|
|
userID := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
|
|
|
|
tok, err := svc.generateTriggerToken(userID)
|
|
if err != nil {
|
|
t.Fatalf("generateTriggerToken returned error: %v", err)
|
|
}
|
|
if !strings.HasPrefix(tok, "Bearer ") {
|
|
t.Fatalf("expected Bearer prefix, got: %s", tok)
|
|
}
|
|
|
|
raw := strings.TrimPrefix(tok, "Bearer ")
|
|
parsed, err := jwt.Parse(raw, func(token *jwt.Token) (any, error) {
|
|
return []byte(secret), nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to parse JWT: %v", err)
|
|
}
|
|
claims, ok := parsed.Claims.(jwt.MapClaims)
|
|
if !ok {
|
|
t.Fatal("expected MapClaims")
|
|
}
|
|
if sub, _ := claims["sub"].(string); sub != userID {
|
|
t.Errorf("expected sub=%s, got=%s", userID, sub)
|
|
}
|
|
if uid, _ := claims["user_id"].(string); uid != userID {
|
|
t.Errorf("expected user_id=%s, got=%s", userID, uid)
|
|
}
|
|
exp, _ := claims["exp"].(float64)
|
|
if exp == 0 {
|
|
t.Fatal("expected non-zero exp")
|
|
}
|
|
expTime := time.Unix(int64(exp), 0)
|
|
if expTime.Before(time.Now().Add(9 * time.Minute)) {
|
|
t.Error("token expires too soon")
|
|
}
|
|
}
|
|
|
|
func TestGenerateTriggerToken_EmptySecret(t *testing.T) {
|
|
svc := &Service{
|
|
jwtSecret: "",
|
|
logger: slog.Default(),
|
|
}
|
|
_, err := svc.generateTriggerToken("user-123")
|
|
if err == nil {
|
|
t.Fatal("expected error for empty secret")
|
|
}
|
|
}
|
|
|
|
func TestGenerateTriggerToken_EmptyUserID(t *testing.T) {
|
|
svc := &Service{
|
|
jwtSecret: "some-secret",
|
|
logger: slog.Default(),
|
|
}
|
|
_, err := svc.generateTriggerToken("")
|
|
if err == nil {
|
|
t.Fatal("expected error for empty user ID")
|
|
}
|
|
}
|