Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,14 +223,22 @@ type User struct {
} `json:"profile"`
}

// Reaction represents an emoji reaction on a Slack message
type Reaction struct {
Name string `json:"name"`
Count int `json:"count"`
Users []string `json:"users"`
}

// Message represents a Slack message
type Message struct {
Type string `json:"type"`
User string `json:"user"`
Text string `json:"text"`
TS string `json:"ts"`
ThreadTS string `json:"thread_ts,omitempty"`
ReplyCount int `json:"reply_count,omitempty"`
Type string `json:"type"`
User string `json:"user"`
Text string `json:"text"`
TS string `json:"ts"`
ThreadTS string `json:"thread_ts,omitempty"`
ReplyCount int `json:"reply_count,omitempty"`
Reactions []Reaction `json:"reactions,omitempty"`
}

// Team represents workspace info
Expand Down
101 changes: 101 additions & 0 deletions internal/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,107 @@ func TestClient_GetThreadReplies_Success(t *testing.T) {
}
}

func TestClient_GetThreadReplies_WithReactions(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := map[string]interface{}{
"ok": true,
"messages": []map[string]interface{}{
{
"ts": "1234567890.123456",
"text": "Original",
"user": "U123",
"reactions": []map[string]interface{}{
{"name": "thumbsup", "count": 2, "users": []string{"U123", "U456"}},
{"name": "heart", "count": 1, "users": []string{"U789"}},
},
},
{
"ts": "1234567890.123457",
"text": "Reply without reactions",
"user": "U456",
},
},
"response_metadata": map[string]string{"next_cursor": ""},
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(resp)
}))
defer server.Close()

client := NewWithConfig(server.URL, "test-token", nil)
messages, err := client.GetThreadReplies("C123", "1234567890.123456", 100)

if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(messages) != 2 {
t.Fatalf("expected 2 messages, got %d", len(messages))
}

// First message should have reactions
if len(messages[0].Reactions) != 2 {
t.Fatalf("expected 2 reactions on first message, got %d", len(messages[0].Reactions))
}
if messages[0].Reactions[0].Name != "thumbsup" {
t.Errorf("expected reaction name 'thumbsup', got %s", messages[0].Reactions[0].Name)
}
if messages[0].Reactions[0].Count != 2 {
t.Errorf("expected reaction count 2, got %d", messages[0].Reactions[0].Count)
}
if len(messages[0].Reactions[0].Users) != 2 {
t.Errorf("expected 2 users on thumbsup reaction, got %d", len(messages[0].Reactions[0].Users))
}
if messages[0].Reactions[1].Name != "heart" {
t.Errorf("expected reaction name 'heart', got %s", messages[0].Reactions[1].Name)
}

// Second message should have no reactions
if len(messages[1].Reactions) != 0 {
t.Errorf("expected 0 reactions on second message, got %d", len(messages[1].Reactions))
}
}

func TestClient_GetChannelHistory_WithReactions(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := map[string]interface{}{
"ok": true,
"messages": []map[string]interface{}{
{
"ts": "1234567890.123456",
"text": "Hello",
"user": "U123",
"reactions": []map[string]interface{}{
{"name": "wave", "count": 3, "users": []string{"U1", "U2", "U3"}},
},
},
},
"response_metadata": map[string]string{"next_cursor": ""},
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(resp)
}))
defer server.Close()

client := NewWithConfig(server.URL, "test-token", nil)
messages, err := client.GetChannelHistory("C123", 20, "", "")

if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(messages) != 1 {
t.Fatalf("expected 1 message, got %d", len(messages))
}
if len(messages[0].Reactions) != 1 {
t.Fatalf("expected 1 reaction, got %d", len(messages[0].Reactions))
}
if messages[0].Reactions[0].Name != "wave" {
t.Errorf("expected reaction name 'wave', got %s", messages[0].Reactions[0].Name)
}
if messages[0].Reactions[0].Count != 3 {
t.Errorf("expected reaction count 3, got %d", messages[0].Reactions[0].Count)
}
}

func TestClient_AddReaction_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !strings.Contains(r.URL.Path, "reactions.add") {
Expand Down
110 changes: 110 additions & 0 deletions internal/cmd/messages/messages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/require"

"github.com/open-cli-collective/slack-chat-api/internal/client"
"github.com/open-cli-collective/slack-chat-api/internal/output"
)

func TestFormatTimestamp(t *testing.T) {
Expand Down Expand Up @@ -483,6 +484,115 @@ func TestRunThread_Success(t *testing.T) {
require.NoError(t, err)
}

func TestRunThread_JSONIncludesReactions(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/conversations.replies":
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"ok": true,
"messages": []map[string]interface{}{
{
"ts": "1234567890.123456",
"user": "U001",
"text": "Original",
"reactions": []map[string]interface{}{
{"name": "thumbsup", "count": 2, "users": []string{"U001", "U002"}},
},
},
{
"ts": "1234567890.123457",
"user": "U002",
"text": "Reply without reactions",
},
},
})
case "/users.info":
mockUserInfoHandler(w, r)
}
}))
defer server.Close()

c := client.NewWithConfig(server.URL, "test-token", nil)
opts := &threadOptions{limit: 100}

// Capture JSON output
output.OutputFormat = output.FormatJSON
defer func() { output.OutputFormat = output.FormatText }()

var buf strings.Builder
output.Writer = &buf
defer func() { output.Writer = os.Stdout }()

err := runThread("C123", "1234567890.123456", opts, c)
require.NoError(t, err)

// Parse the JSON output
var messages []client.Message
err = json.Unmarshal([]byte(buf.String()), &messages)
require.NoError(t, err)

require.Len(t, messages, 2)

// First message should have reactions
require.Len(t, messages[0].Reactions, 1)
assert.Equal(t, "thumbsup", messages[0].Reactions[0].Name)
assert.Equal(t, 2, messages[0].Reactions[0].Count)
assert.Equal(t, []string{"U001", "U002"}, messages[0].Reactions[0].Users)

// Second message should have no reactions
assert.Empty(t, messages[1].Reactions)
}

func TestRunHistory_JSONIncludesReactions(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/conversations.history":
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"ok": true,
"messages": []map[string]interface{}{
{
"ts": "1234567890.123456",
"user": "U001",
"text": "Hello",
"reactions": []map[string]interface{}{
{"name": "wave", "count": 1, "users": []string{"U002"}},
{"name": "heart", "count": 3, "users": []string{"U001", "U002", "U003"}},
},
},
},
})
case "/users.info":
mockUserInfoHandler(w, r)
}
}))
defer server.Close()

c := client.NewWithConfig(server.URL, "test-token", nil)
opts := &historyOptions{limit: 20}

// Capture JSON output
output.OutputFormat = output.FormatJSON
defer func() { output.OutputFormat = output.FormatText }()

var buf strings.Builder
output.Writer = &buf
defer func() { output.Writer = os.Stdout }()

err := runHistory("C123", opts, c)
require.NoError(t, err)

// Parse the JSON output
var messages []client.Message
err = json.Unmarshal([]byte(buf.String()), &messages)
require.NoError(t, err)

require.Len(t, messages, 1)
require.Len(t, messages[0].Reactions, 2)
assert.Equal(t, "wave", messages[0].Reactions[0].Name)
assert.Equal(t, "heart", messages[0].Reactions[1].Name)
assert.Equal(t, 3, messages[0].Reactions[1].Count)
}

func TestRunReact_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/reactions.add", r.URL.Path)
Expand Down