mirror of
https://github.com/owncast/owncast.git
synced 2025-11-01 10:55:57 +08:00
* Initial plan * Add server status as default field in all webhooks Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Fix goimports linter error by removing trailing whitespace Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Move serverURL from status object to separate webhook field per feedback Per code review feedback, serverURL is a configuration value, not a status property. This change: - Removes ServerURL from models.Status struct - Adds ServerURL as separate field in WebhookEvent - Populates ServerURL directly when sending webhooks using configrepository.GetServerURL() - Updates all tests to expect new structure This provides the same functionality (server URL in all webhooks) while correctly treating it as configuration rather than status. Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Add omitempty tag to ServerURL field in WebhookEvent struct Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Fix webhook duplication by moving status to eventData for all events Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Restore type safety to webhook EventData using proper typed structs Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Move ServerURL from top-level WebhookEvent to eventData for all webhook types Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Update core/webhooks/webhooks.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Create BaseWebhookData struct for common webhook fields using struct embedding Co-authored-by: gabek <414923+gabek@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: gabek <414923+gabek@users.noreply.github.com> Co-authored-by: Gabe Kangas <gabek@real-ity.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
315 lines
7.5 KiB
Go
315 lines
7.5 KiB
Go
package webhooks
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/owncast/owncast/core/chat/events"
|
|
"github.com/owncast/owncast/models"
|
|
"github.com/owncast/owncast/persistence/configrepository"
|
|
"github.com/owncast/owncast/persistence/webhookrepository"
|
|
)
|
|
|
|
func TestSendChatEvent(t *testing.T) {
|
|
timestamp := time.Unix(72, 6).UTC()
|
|
user := models.User{
|
|
ID: "user id",
|
|
DisplayName: "display name",
|
|
DisplayColor: 4,
|
|
CreatedAt: time.Unix(3, 26).UTC(),
|
|
DisabledAt: nil,
|
|
PreviousNames: []string{"somebody"},
|
|
NameChangedAt: nil,
|
|
Scopes: []string{},
|
|
IsBot: false,
|
|
AuthenticatedAt: nil,
|
|
Authenticated: false,
|
|
}
|
|
|
|
checkPayload(t, models.MessageSent, func() {
|
|
SendChatEvent(&events.UserMessageEvent{
|
|
Event: events.Event{
|
|
Type: events.MessageSent,
|
|
ID: "id",
|
|
Timestamp: timestamp,
|
|
},
|
|
UserEvent: events.UserEvent{
|
|
User: &user,
|
|
ClientID: 51,
|
|
HiddenAt: nil,
|
|
},
|
|
MessageEvent: events.MessageEvent{
|
|
OutboundEvent: nil,
|
|
Body: "body",
|
|
RawBody: "raw body",
|
|
},
|
|
})
|
|
}, `{
|
|
"body": "body",
|
|
"clientId": 51,
|
|
"id": "id",
|
|
"rawBody": "raw body",
|
|
"serverURL": "http://localhost:8080",
|
|
"status": {
|
|
"lastConnectTime": null,
|
|
"lastDisconnectTime": null,
|
|
"online": true,
|
|
"overallMaxViewerCount": 420,
|
|
"sessionMaxViewerCount": 69,
|
|
"streamTitle": "my stream",
|
|
"versionNumber": "1.2.3",
|
|
"viewerCount": 5
|
|
},
|
|
"timestamp": "1970-01-01T00:01:12.000000006Z",
|
|
"user": {
|
|
"authenticated": false,
|
|
"createdAt": "1970-01-01T00:00:03.000000026Z",
|
|
"displayColor": 4,
|
|
"displayName": "display name",
|
|
"id": "user id",
|
|
"isBot": false,
|
|
"previousNames": ["somebody"]
|
|
},
|
|
"visible": true
|
|
}`)
|
|
}
|
|
|
|
func TestSendChatEventUsernameChanged(t *testing.T) {
|
|
timestamp := time.Unix(72, 6).UTC()
|
|
user := models.User{
|
|
ID: "user id",
|
|
DisplayName: "display name",
|
|
DisplayColor: 4,
|
|
CreatedAt: time.Unix(3, 26).UTC(),
|
|
DisabledAt: nil,
|
|
PreviousNames: []string{"somebody"},
|
|
NameChangedAt: nil,
|
|
Scopes: []string{},
|
|
IsBot: false,
|
|
AuthenticatedAt: nil,
|
|
Authenticated: false,
|
|
}
|
|
|
|
checkPayload(t, models.UserNameChanged, func() {
|
|
SendChatEventUsernameChanged(events.NameChangeEvent{
|
|
Event: events.Event{
|
|
Type: events.UserNameChanged,
|
|
ID: "id",
|
|
Timestamp: timestamp,
|
|
},
|
|
UserEvent: events.UserEvent{
|
|
User: &user,
|
|
ClientID: 51,
|
|
HiddenAt: nil,
|
|
},
|
|
NewName: "new name",
|
|
})
|
|
}, `{
|
|
"id": "id",
|
|
"newName": "new name",
|
|
"serverURL": "http://localhost:8080",
|
|
"status": {
|
|
"lastConnectTime": null,
|
|
"lastDisconnectTime": null,
|
|
"online": true,
|
|
"overallMaxViewerCount": 420,
|
|
"sessionMaxViewerCount": 69,
|
|
"streamTitle": "my stream",
|
|
"versionNumber": "1.2.3",
|
|
"viewerCount": 5
|
|
},
|
|
"timestamp": "1970-01-01T00:01:12.000000006Z",
|
|
"user": {
|
|
"authenticated": false,
|
|
"createdAt": "1970-01-01T00:00:03.000000026Z",
|
|
"displayColor": 4,
|
|
"displayName": "display name",
|
|
"id": "user id",
|
|
"isBot": false,
|
|
"previousNames": ["somebody"]
|
|
}
|
|
}`)
|
|
}
|
|
|
|
func TestSendChatEventUserJoined(t *testing.T) {
|
|
timestamp := time.Unix(72, 6).UTC()
|
|
user := models.User{
|
|
ID: "user id",
|
|
DisplayName: "display name",
|
|
DisplayColor: 4,
|
|
CreatedAt: time.Unix(3, 26).UTC(),
|
|
DisabledAt: nil,
|
|
PreviousNames: []string{"somebody"},
|
|
NameChangedAt: nil,
|
|
Scopes: []string{},
|
|
IsBot: false,
|
|
AuthenticatedAt: nil,
|
|
Authenticated: false,
|
|
}
|
|
|
|
checkPayload(t, models.UserJoined, func() {
|
|
SendChatEventUserJoined(events.UserJoinedEvent{
|
|
Event: events.Event{
|
|
Type: events.UserJoined,
|
|
ID: "id",
|
|
Timestamp: timestamp,
|
|
},
|
|
UserEvent: events.UserEvent{
|
|
User: &user,
|
|
ClientID: 51,
|
|
HiddenAt: nil,
|
|
},
|
|
})
|
|
}, `{
|
|
"id": "id",
|
|
"serverURL": "http://localhost:8080",
|
|
"status": {
|
|
"lastConnectTime": null,
|
|
"lastDisconnectTime": null,
|
|
"online": true,
|
|
"overallMaxViewerCount": 420,
|
|
"sessionMaxViewerCount": 69,
|
|
"streamTitle": "my stream",
|
|
"versionNumber": "1.2.3",
|
|
"viewerCount": 5
|
|
},
|
|
"timestamp": "1970-01-01T00:01:12.000000006Z",
|
|
"user": {
|
|
"authenticated": false,
|
|
"createdAt": "1970-01-01T00:00:03.000000026Z",
|
|
"displayColor": 4,
|
|
"displayName": "display name",
|
|
"id": "user id",
|
|
"isBot": false,
|
|
"previousNames": ["somebody"]
|
|
}
|
|
}`)
|
|
}
|
|
|
|
func TestSendChatEventSetMessageVisibility(t *testing.T) {
|
|
timestamp := time.Unix(72, 6).UTC()
|
|
|
|
checkPayload(t, models.VisibiltyToggled, func() {
|
|
SendChatEventSetMessageVisibility(events.SetMessageVisibilityEvent{
|
|
Event: events.Event{
|
|
Type: events.VisibiltyUpdate,
|
|
ID: "id",
|
|
Timestamp: timestamp,
|
|
},
|
|
UserMessageEvent: events.UserMessageEvent{},
|
|
MessageIDs: []string{"message1", "message2"},
|
|
Visible: false,
|
|
})
|
|
}, `{
|
|
"id": "id",
|
|
"ids": [
|
|
"message1",
|
|
"message2"
|
|
],
|
|
"serverURL": "http://localhost:8080",
|
|
"status": {
|
|
"lastConnectTime": null,
|
|
"lastDisconnectTime": null,
|
|
"online": true,
|
|
"overallMaxViewerCount": 420,
|
|
"sessionMaxViewerCount": 69,
|
|
"streamTitle": "my stream",
|
|
"versionNumber": "1.2.3",
|
|
"viewerCount": 5
|
|
},
|
|
"timestamp": "1970-01-01T00:01:12.000000006Z",
|
|
"user": null,
|
|
"visible": false
|
|
}`)
|
|
}
|
|
|
|
// TestWebhookHasServerStatus verifies that all webhook events include server status
|
|
func TestWebhookHasServerStatus(t *testing.T) {
|
|
// Set up server configuration
|
|
configRepo := configrepository.Get()
|
|
configRepo.SetServerURL("http://localhost:8080")
|
|
|
|
eventChannel := make(chan WebhookEvent)
|
|
|
|
// Set up a server.
|
|
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
data := WebhookEvent{}
|
|
json.NewDecoder(r.Body).Decode(&data)
|
|
eventChannel <- data
|
|
}))
|
|
defer svr.Close()
|
|
|
|
webhooksRepo := webhookrepository.Get()
|
|
|
|
// Subscribe to the webhook.
|
|
hook, err := webhooksRepo.InsertWebhook(svr.URL, []models.EventType{models.UserJoined})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() {
|
|
if err := webhooksRepo.DeleteWebhook(hook); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}()
|
|
|
|
// Send a chat event
|
|
timestamp := time.Unix(72, 6).UTC()
|
|
user := models.User{
|
|
ID: "user id",
|
|
DisplayName: "display name",
|
|
DisplayColor: 4,
|
|
CreatedAt: time.Unix(3, 26).UTC(),
|
|
DisabledAt: nil,
|
|
PreviousNames: []string{"somebody"},
|
|
NameChangedAt: nil,
|
|
Scopes: []string{},
|
|
IsBot: false,
|
|
AuthenticatedAt: nil,
|
|
Authenticated: false,
|
|
}
|
|
|
|
SendChatEventUserJoined(events.UserJoinedEvent{
|
|
Event: events.Event{
|
|
Type: events.UserJoined,
|
|
ID: "id",
|
|
Timestamp: timestamp,
|
|
},
|
|
UserEvent: events.UserEvent{
|
|
User: &user,
|
|
ClientID: 51,
|
|
HiddenAt: nil,
|
|
},
|
|
})
|
|
|
|
// Capture the event
|
|
event := <-eventChannel
|
|
|
|
// Verify the webhook event has a status field in eventData
|
|
eventData, ok := event.EventData.(map[string]interface{})
|
|
if !ok {
|
|
t.Error("Expected EventData to be a map")
|
|
}
|
|
|
|
status, ok := eventData["status"].(map[string]interface{})
|
|
if !ok {
|
|
t.Error("Expected eventData to contain status field")
|
|
}
|
|
|
|
versionNumber, ok := status["versionNumber"].(string)
|
|
if !ok || versionNumber == "" {
|
|
t.Error("Expected eventData.status to have versionNumber, but it was empty")
|
|
}
|
|
|
|
serverURL, ok := eventData["serverURL"].(string)
|
|
if !ok || serverURL == "" {
|
|
t.Error("Expected eventData to have serverURL, but it was empty")
|
|
}
|
|
|
|
if event.Type != models.UserJoined {
|
|
t.Errorf("Expected event type %v but got %v", models.UserJoined, event.Type)
|
|
}
|
|
}
|