162 lines
6.1 KiB
Go
162 lines
6.1 KiB
Go
package notification
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const expectedPushPayloadMappingTable = `| ` + "`notification_type`" + ` | FlatBuffers table | Payload fields |
|
|
| --- | --- | --- |
|
|
| ` + "`game.turn.ready`" + ` | ` + "`notification.GameTurnReadyEvent`" + ` | ` + "`game_id`" + `, ` + "`turn_number`" + ` |
|
|
| ` + "`game.finished`" + ` | ` + "`notification.GameFinishedEvent`" + ` | ` + "`game_id`" + `, ` + "`final_turn_number`" + ` |
|
|
| ` + "`lobby.application.submitted`" + ` | ` + "`notification.LobbyApplicationSubmittedEvent`" + ` | ` + "`game_id`" + `, ` + "`applicant_user_id`" + ` |
|
|
| ` + "`lobby.membership.approved`" + ` | ` + "`notification.LobbyMembershipApprovedEvent`" + ` | ` + "`game_id`" + ` |
|
|
| ` + "`lobby.membership.rejected`" + ` | ` + "`notification.LobbyMembershipRejectedEvent`" + ` | ` + "`game_id`" + ` |
|
|
| ` + "`lobby.invite.created`" + ` | ` + "`notification.LobbyInviteCreatedEvent`" + ` | ` + "`game_id`" + `, ` + "`inviter_user_id`" + ` |
|
|
| ` + "`lobby.invite.redeemed`" + ` | ` + "`notification.LobbyInviteRedeemedEvent`" + ` | ` + "`game_id`" + `, ` + "`invitee_user_id`" + ` |`
|
|
|
|
var expectedPushPayloadSchemaTableNames = []string{
|
|
"GameTurnReadyEvent",
|
|
"GameFinishedEvent",
|
|
"LobbyApplicationSubmittedEvent",
|
|
"LobbyMembershipApprovedEvent",
|
|
"LobbyMembershipRejectedEvent",
|
|
"LobbyInviteCreatedEvent",
|
|
"LobbyInviteRedeemedEvent",
|
|
}
|
|
|
|
var expectedPushPayloadSchemaFields = map[string][]string{
|
|
"GameTurnReadyEvent": {
|
|
"game_id:string;",
|
|
"turn_number:int64;",
|
|
},
|
|
"GameFinishedEvent": {
|
|
"game_id:string;",
|
|
"final_turn_number:int64;",
|
|
},
|
|
"LobbyApplicationSubmittedEvent": {
|
|
"game_id:string;",
|
|
"applicant_user_id:string;",
|
|
},
|
|
"LobbyMembershipApprovedEvent": {
|
|
"game_id:string;",
|
|
},
|
|
"LobbyMembershipRejectedEvent": {
|
|
"game_id:string;",
|
|
},
|
|
"LobbyInviteCreatedEvent": {
|
|
"game_id:string;",
|
|
"inviter_user_id:string;",
|
|
},
|
|
"LobbyInviteRedeemedEvent": {
|
|
"game_id:string;",
|
|
"invitee_user_id:string;",
|
|
},
|
|
}
|
|
|
|
var expectedPushPayloadGeneratedFiles = []string{
|
|
"GameFinishedEvent.go",
|
|
"GameTurnReadyEvent.go",
|
|
"LobbyApplicationSubmittedEvent.go",
|
|
"LobbyInviteCreatedEvent.go",
|
|
"LobbyInviteRedeemedEvent.go",
|
|
"LobbyMembershipApprovedEvent.go",
|
|
"LobbyMembershipRejectedEvent.go",
|
|
}
|
|
|
|
var expectedPushPayloadDocumentationSnippets = []string{
|
|
"Only the seven user-facing push notification types above are represented in `notification.fbs`.",
|
|
"`geo.review_recommended`, `game.generation_failed`, `lobby.runtime_paused_after_start`, and `lobby.invite.expired` remain outside this schema because they are email-only in v1.",
|
|
"`notification_type` alone determines the concrete FlatBuffers table.",
|
|
"No extra envelope or FlatBuffers `union` is added in v1.",
|
|
"The push payload must stay lightweight and must not attempt to mirror full game, lobby, or profile state.",
|
|
"`game_name`, human-readable user names, and other full business-state fields stay out of the push schema.",
|
|
}
|
|
|
|
func TestNotificationPushPayloadSchemaFreezesTablesAndFields(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
schema := loadTextFile(t, filepath.Join("..", "pkg", "schema", "fbs", "notification.fbs"))
|
|
require.Contains(t, schema, "namespace notification;")
|
|
require.Contains(t, schema, "root_type GameTurnReadyEvent;")
|
|
require.NotContains(t, schema, "union ")
|
|
|
|
tablePattern := regexp.MustCompile(`(?m)^table ([A-Za-z0-9_]+) \{$`)
|
|
matches := tablePattern.FindAllStringSubmatch(schema, -1)
|
|
actualTableNames := make([]string, 0, len(matches))
|
|
for _, match := range matches {
|
|
actualTableNames = append(actualTableNames, match[1])
|
|
}
|
|
|
|
require.Equal(t, expectedPushPayloadSchemaTableNames, actualTableNames)
|
|
|
|
for _, tableName := range expectedPushPayloadSchemaTableNames {
|
|
tableBody := extractFlatBuffersTableBody(t, schema, tableName)
|
|
for _, field := range expectedPushPayloadSchemaFields[tableName] {
|
|
require.Contains(t, tableBody, field)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNotificationPushPayloadGeneratedBindingsStayInSync(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dirPath := filepath.Join(moduleRoot(t), "..", "pkg", "schema", "fbs", "notification")
|
|
entries, err := os.ReadDir(dirPath)
|
|
require.NoError(t, err)
|
|
|
|
actualFiles := make([]string, 0, len(entries))
|
|
for _, entry := range entries {
|
|
require.Falsef(t, entry.IsDir(), "unexpected directory in generated bindings: %s", entry.Name())
|
|
actualFiles = append(actualFiles, entry.Name())
|
|
|
|
fileContents := loadTextFile(t, filepath.Join("..", "pkg", "schema", "fbs", "notification", entry.Name()))
|
|
require.Contains(t, fileContents, "// Code generated by the FlatBuffers compiler. DO NOT EDIT.")
|
|
require.Contains(t, fileContents, "package notification")
|
|
}
|
|
|
|
sort.Strings(actualFiles)
|
|
require.Equal(t, expectedPushPayloadGeneratedFiles, actualFiles)
|
|
}
|
|
|
|
func TestNotificationPushPayloadDocsStayInSync(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
readme := loadTextFile(t, "README.md")
|
|
flowsDoc := loadTextFile(t, filepath.Join("docs", "flows.md"))
|
|
examplesDoc := loadTextFile(t, filepath.Join("docs", "examples.md"))
|
|
docsIndex := loadTextFile(t, filepath.Join("docs", "README.md"))
|
|
normalizedReadme := normalizeWhitespace(readme)
|
|
normalizedFlowsDoc := normalizeWhitespace(flowsDoc)
|
|
normalizedExamplesDoc := normalizeWhitespace(examplesDoc)
|
|
|
|
require.Contains(t, readme, expectedPushPayloadMappingTable)
|
|
require.Contains(t, docsIndex, "- [Main flows](flows.md)")
|
|
require.Contains(t, docsIndex, "- [Configuration and contract examples](examples.md)")
|
|
|
|
for _, snippet := range expectedPushPayloadDocumentationSnippets {
|
|
normalizedSnippet := normalizeWhitespace(snippet)
|
|
require.Contains(t, normalizedReadme, normalizedSnippet)
|
|
}
|
|
|
|
require.Contains(t, normalizedFlowsDoc, normalizeWhitespace("encode FlatBuffers notification payload"))
|
|
require.Contains(t, normalizedExamplesDoc, normalizeWhitespace("payload_bytes '<flatbuffers-bytes>'"))
|
|
}
|
|
|
|
func extractFlatBuffersTableBody(t *testing.T, schema, tableName string) string {
|
|
t.Helper()
|
|
|
|
pattern := regexp.MustCompile(`(?s)table ` + regexp.QuoteMeta(tableName) + ` \{(.*?)\}`)
|
|
match := pattern.FindStringSubmatch(schema)
|
|
if match == nil {
|
|
require.FailNowf(t, "test failed", "table %s not found in schema", tableName)
|
|
}
|
|
|
|
return match[1]
|
|
}
|