package automation import ( "context" "testing" "github.com/gochat/gochat/internal/model" ) // =========================== // ResolveTemplateVars tests // =========================== func TestResolveTemplateVars_ContactName(t *testing.T) { ctx := TemplateContext{ Contact: &model.Contact{Name: "Alice Smith"}, } result := ResolveTemplateVars("Hello {{contact.name}}!", ctx) if result != "Hello Alice Smith!" { t.Fatalf("expected 'Hello Alice Smith!', got '%s'", result) } } func TestResolveTemplateVars_ConversationStatus(t *testing.T) { ctx := TemplateContext{ Conversation: &model.Conversation{Status: "open"}, } result := ResolveTemplateVars("Status: {{conversation.status}}", ctx) if result != "Status: open" { t.Fatalf("expected 'Status: open', got '%s'", result) } } func TestResolveTemplateVars_AgentName(t *testing.T) { ctx := TemplateContext{ Assignee: &model.User{Name: "Bob Agent"}, } result := ResolveTemplateVars("Assigned to {{agent.name}}", ctx) if result != "Assigned to Bob Agent" { t.Fatalf("expected 'Assigned to Bob Agent', got '%s'", result) } } func TestResolveTemplateVars_MultipleVariables(t *testing.T) { ctx := TemplateContext{ Contact: &model.Contact{Name: "Alice", Email: "alice@example.com"}, Conversation: &model.Conversation{Status: "open"}, Assignee: &model.User{Name: "Bob"}, } result := ResolveTemplateVars("Hi {{contact.name}}, your ticket {{conversation.status}} is handled by {{agent.name}}. Email: {{contact.email}}", ctx) expected := "Hi Alice, your ticket open is handled by Bob. Email: alice@example.com" if result != expected { t.Fatalf("expected '%s', got '%s'", expected, result) } } func TestResolveTemplateVars_NilContext(t *testing.T) { ctx := TemplateContext{} // all nil result := ResolveTemplateVars("Hello {{contact.name}}, status={{conversation.status}}", ctx) // Unresolved variables → empty string (Chatwoot convention) if result != "Hello , status=" { t.Fatalf("expected 'Hello , status=', got '%s'", result) } } func TestResolveTemplateVars_NilAssignee(t *testing.T) { ctx := TemplateContext{ Conversation: &model.Conversation{Status: "open"}, Contact: &model.Contact{Name: "Alice"}, // Assignee is nil } result := ResolveTemplateVars("{{agent.name}} handling {{contact.name}}'s {{conversation.status}} ticket", ctx) if result != " handling Alice's open ticket" { t.Fatalf("expected ' handling Alice\\'s open ticket', got '%s'", result) } } func TestResolveTemplateVars_NoVariables(t *testing.T) { ctx := TemplateContext{ Contact: &model.Contact{Name: "Alice"}, } result := ResolveTemplateVars("Static message with no variables", ctx) if result != "Static message with no variables" { t.Fatalf("expected unchanged string, got '%s'", result) } } func TestResolveTemplateVars_UnknownObject(t *testing.T) { ctx := TemplateContext{} result := ResolveTemplateVars("{{unknown.attr}} test", ctx) if result != " test" { t.Fatalf("expected ' test' (unknown object → empty), got '%s'", result) } } func TestResolveTemplateVars_UnknownAttribute(t *testing.T) { ctx := TemplateContext{ Contact: &model.Contact{Name: "Alice"}, } result := ResolveTemplateVars("{{contact.nonexistent}} test", ctx) if result != " test" { t.Fatalf("expected ' test' (unknown attr → empty), got '%s'", result) } } func TestResolveTemplateVars_InboxName(t *testing.T) { ctx := TemplateContext{ Inbox: &model.Inbox{Name: "Support Chat"}, } result := ResolveTemplateVars("From {{inbox.name}}", ctx) if result != "From Support Chat" { t.Fatalf("expected 'From Support Chat', got '%s'", result) } } func TestResolveTemplateVars_TeamName(t *testing.T) { ctx := TemplateContext{ Team: &model.Team{Name: "Escalation"}, } result := ResolveTemplateVars("Team: {{team.name}}", ctx) if result != "Team: Escalation" { t.Fatalf("expected 'Team: Escalation', got '%s'", result) } } func TestResolveTemplateVars_AccountName(t *testing.T) { ctx := TemplateContext{ Account: &model.Account{Name: "Acme Corp"}, } result := ResolveTemplateVars("Account: {{account.name}}", ctx) if result != "Account: Acme Corp" { t.Fatalf("expected 'Account: Acme Corp', got '%s'", result) } } func TestResolveTemplateVars_ConversationID(t *testing.T) { ctx := TemplateContext{ Conversation: &model.Conversation{Status: "open"}, } // Need to set ID — in real usage GORM populates it // For this test we test the format string path result := ResolveTemplateVars("Ticket #{{conversation.id}}", ctx) // ID is 0 for a non-persisted conversation if result != "Ticket #0" { t.Fatalf("expected 'Ticket #0', got '%s'", result) } } func TestResolveTemplateVars_ContactPhoneNumber(t *testing.T) { ctx := TemplateContext{ Contact: &model.Contact{PhoneNumber: "+1-555-0123"}, } result := ResolveTemplateVars("Call {{contact.phone_number}}", ctx) if result != "Call +1-555-0123" { t.Fatalf("expected 'Call +1-555-0123', got '%s'", result) } } // =========================== // ResolveActionParams tests // =========================== func TestResolveActionParams_StringValues(t *testing.T) { ctx := TemplateContext{ Contact: &model.Contact{Name: "Alice"}, } params := map[string]interface{}{ "content": "Hello {{contact.name}}!", "status": "resolved", "assignee": 42, // non-string, should pass through unchanged } resolved := ResolveActionParams(params, ctx) if resolved["content"] != "Hello Alice!" { t.Fatalf("expected 'Hello Alice!', got '%s'", resolved["content"]) } if resolved["status"] != "resolved" { t.Fatalf("expected 'resolved', got '%s'", resolved["status"]) } if resolved["assignee"] != 42 { t.Fatalf("expected 42, got %v", resolved["assignee"]) } } func TestResolveActionParams_NilMap(t *testing.T) { resolved := ResolveActionParams(nil, TemplateContext{}) if resolved != nil { t.Fatalf("expected nil map, got non-nil") } } func TestResolveActionParams_EmptyMap(t *testing.T) { resolved := ResolveActionParams(map[string]interface{}{}, TemplateContext{}) if len(resolved) != 0 { t.Fatalf("expected empty map, got %d entries", len(resolved)) } } // =========================== // TemplateContextBuilder tests (DB-based) // =========================== func TestTemplateContextBuilder_Build(t *testing.T) { dbProvider := setupAutomationTestDBProvider(t) accountID, userID := seedTestAccount(dbProvider.DB(), t) builder := NewTemplateContextBuilder(dbProvider) // Create inbox inbox := &model.Inbox{ AccountID: accountID, Name: "Support Inbox", ChannelType: "web_widget", ChannelID: 1, } if err := dbProvider.DB().Create(inbox).Error; err != nil { t.Fatalf("failed to create inbox: %v", err) } // Create contact contact := &model.Contact{ AccountID: accountID, Name: "Jane Customer", Email: "jane@test.com", } if err := dbProvider.DB().Select("AccountID", "Name", "Email").Create(contact).Error; err != nil { t.Fatalf("failed to create contact: %v", err) } // Create conversation with assignee assigneeID := userID conv := &model.Conversation{ AccountID: accountID, InboxID: inbox.ID, ContactID: contact.ID, AssigneeID: &assigneeID, Status: "open", Priority: "medium", ChannelType: "web_widget", Channel: "web", UUID: "test-uuid-template-build", } if err := dbProvider.DB().Select( "AccountID", "InboxID", "ContactID", "AssigneeID", "Status", "Priority", "ChannelType", "Channel", "UUID", ).Create(conv).Error; err != nil { t.Fatalf("failed to create conversation: %v", err) } // Build template context tctx, err := builder.Build(testContext, conv.ID) if err != nil { t.Fatalf("expected no error, got: %v", err) } // Verify conversation loaded if tctx.Conversation == nil || tctx.Conversation.Status != "open" { t.Fatalf("expected conversation with status=open, got nil or wrong status") } // Verify contact loaded if tctx.Contact == nil || tctx.Contact.Name != "Jane Customer" { t.Fatalf("expected contact with name=Jane Customer, got nil or wrong name") } // Verify assignee loaded if tctx.Assignee == nil || tctx.Assignee.Name != "Test User" { t.Fatalf("expected assignee with name=Test User, got nil or wrong name") } // Verify inbox loaded if tctx.Inbox == nil || tctx.Inbox.Name != "Support Inbox" { t.Fatalf("expected inbox with name=Support Inbox, got nil or wrong name") } // Verify account loaded if tctx.Account == nil || tctx.Account.Name != "Test Account" { t.Fatalf("expected account with name=Test Account, got nil or wrong name") } // Now resolve template vars with this context result := ResolveTemplateVars("Hi {{contact.name}}, your {{conversation.status}} ticket in {{inbox.name}} is assigned to {{agent.name}}.", tctx) expected := "Hi Jane Customer, your open ticket in Support Inbox is assigned to Test User." if result != expected { t.Fatalf("expected '%s', got '%s'", expected, result) } } func TestTemplateContextBuilder_ConversationNotFound(t *testing.T) { dbProvider := setupAutomationTestDBProvider(t) builder := NewTemplateContextBuilder(dbProvider) _, err := builder.Build(testContext, 9999) // non-existent ID if err == nil { t.Fatalf("expected error for non-existent conversation, got nil") } } func TestTemplateContextBuilder_NoAssignee(t *testing.T) { dbProvider := setupAutomationTestDBProvider(t) accountID, _ := seedTestAccount(dbProvider.DB(), t) builder := NewTemplateContextBuilder(dbProvider) // Create inbox inbox := &model.Inbox{ AccountID: accountID, Name: "Support Inbox", ChannelType: "web_widget", ChannelID: 1, } if err := dbProvider.DB().Create(inbox).Error; err != nil { t.Fatalf("failed to create inbox: %v", err) } // Create contact contact := &model.Contact{ AccountID: accountID, Name: "Solo Customer", Email: "solo@test.com", } if err := dbProvider.DB().Select("AccountID", "Name", "Email").Create(contact).Error; err != nil { t.Fatalf("failed to create contact: %v", err) } // Create conversation WITHOUT assignee conv := &model.Conversation{ AccountID: accountID, InboxID: inbox.ID, ContactID: contact.ID, AssigneeID: nil, Status: "pending", Priority: "low", ChannelType: "web_widget", Channel: "web", UUID: "test-uuid-no-assignee", } if err := dbProvider.DB().Select( "AccountID", "InboxID", "ContactID", "Status", "Priority", "ChannelType", "Channel", "UUID", ).Create(conv).Error; err != nil { t.Fatalf("failed to create conversation: %v", err) } tctx, err := builder.Build(testContext, conv.ID) if err != nil { t.Fatalf("expected no error, got: %v", err) } if tctx.Assignee != nil { t.Fatalf("expected nil assignee for unassigned conversation, got non-nil") } // {{agent.name}} should resolve to empty string result := ResolveTemplateVars("Assigned: {{agent.name}}", tctx) if result != "Assigned: " { t.Fatalf("expected 'Assigned: ', got '%s'", result) } } // testContext is a reusable context for DB-based tests. var testContext = context.Background()