| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921 |
- package graph
- import (
- "encoding/json"
- "fmt"
- "strings"
- "testing"
- "time"
- "github.com/99designs/gqlgen/client"
- "github.com/99designs/gqlgen/graphql/handler"
- "github.com/bradleyjkemp/cupaloy/v2"
- "gogs.dmsc.dev/arp/auth"
- "gogs.dmsc.dev/arp/graph/model"
- "gogs.dmsc.dev/arp/graph/testutil"
- "gorm.io/gorm"
- )
- var snapshotter = cupaloy.New(cupaloy.SnapshotSubdirectory("testdata/snapshots"))
- type TestClient struct {
- client *client.Client
- db *gorm.DB
- token string
- }
- type IDTracker struct {
- Permissions map[string]string
- Roles map[string]string
- Users map[string]string
- TaskStatuses map[string]string
- Services map[string]string
- Tasks map[string]string
- Notes map[string]string
- Channels []string
- Messages []string
- }
- func NewIDTracker() *IDTracker {
- return &IDTracker{
- Permissions: make(map[string]string),
- Roles: make(map[string]string),
- Users: make(map[string]string),
- TaskStatuses: make(map[string]string),
- Services: make(map[string]string),
- Tasks: make(map[string]string),
- Notes: make(map[string]string),
- Channels: make([]string, 0),
- Messages: make([]string, 0),
- }
- }
- func setupTestClient(t *testing.T) (*TestClient, *IDTracker) {
- // Setup and bootstrap the database with initial data from init_tests.sql
- db, err := testutil.SetupAndBootstrapTestDB()
- if err != nil {
- t.Fatalf("Failed to setup test database: %v", err)
- }
- resolver := &Resolver{DB: db}
- schema := NewExecutableSchema(Config{Resolvers: resolver})
- srv := handler.NewDefaultServer(schema)
- // Wrap with auth middleware
- authSrv := auth.AuthMiddleware(srv)
- gqlClient := client.New(authSrv)
- // Login as admin to get a token
- var loginResponse struct {
- Login struct {
- Token string `json:"token"`
- User struct {
- ID string `json:"id"`
- Email string `json:"email"`
- } `json:"user"`
- } `json:"login"`
- }
- loginQuery := `mutation { login(email: "admin@example.com", password: "secret123") { token user { id email } } }`
- err = gqlClient.Post(loginQuery, &loginResponse)
- if err != nil {
- t.Fatalf("Failed to login as admin: %v", err)
- }
- if loginResponse.Login.Token == "" {
- t.Fatal("Login returned empty token")
- }
- // Create authenticated client
- authClient := client.New(authSrv, client.AddHeader("Authorization", "Bearer "+loginResponse.Login.Token))
- // Initialize tracker with bootstrapped data
- tracker := NewIDTracker()
- // Track bootstrapped admin user
- tracker.Users["admin@example.com"] = loginResponse.Login.User.ID
- // Fetch and track bootstrapped permissions
- var permsResponse struct {
- Permissions []struct {
- ID string `json:"id"`
- Code string `json:"code"`
- } `json:"permissions"`
- }
- authClient.Post(`query { permissions { id code } }`, &permsResponse)
- for _, perm := range permsResponse.Permissions {
- tracker.Permissions[perm.Code] = perm.ID
- }
- // Fetch and track bootstrapped roles
- var rolesResponse struct {
- Roles []struct {
- ID string `json:"id"`
- Name string `json:"name"`
- } `json:"roles"`
- }
- authClient.Post(`query { roles { id name } }`, &rolesResponse)
- for _, role := range rolesResponse.Roles {
- tracker.Roles[role.Name] = role.ID
- }
- // Fetch and track bootstrapped task statuses
- var statusesResponse struct {
- TaskStatuses []struct {
- ID string `json:"id"`
- Code string `json:"code"`
- } `json:"taskStatuses"`
- }
- authClient.Post(`query { taskStatuses { id code } }`, &statusesResponse)
- for _, status := range statusesResponse.TaskStatuses {
- tracker.TaskStatuses[status.Code] = status.ID
- }
- return &TestClient{client: authClient, db: db, token: loginResponse.Login.Token}, tracker
- }
- func normalizeJSON(jsonStr string) string {
- var data interface{}
- if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
- return jsonStr
- }
- normalizeData(data)
- bytes, _ := json.MarshalIndent(data, "", " ")
- return string(bytes)
- }
- func normalizeData(data interface{}) {
- switch v := data.(type) {
- case map[string]interface{}:
- delete(v, "id")
- delete(v, "ID")
- delete(v, "createdAt")
- delete(v, "updatedAt")
- delete(v, "sentAt")
- delete(v, "createdByID")
- delete(v, "userId")
- delete(v, "serviceId")
- delete(v, "statusId")
- delete(v, "assigneeId")
- delete(v, "conversationId")
- delete(v, "senderId")
- for _, val := range v {
- normalizeData(val)
- }
- case []interface{}:
- for _, item := range v {
- normalizeData(item)
- }
- }
- }
- func snapshotResult(t *testing.T, name string, jsonStr string) {
- normalized := normalizeJSON(jsonStr)
- snapshotter.SnapshotT(t, name, normalized)
- }
- func TestIntegration_Bootstrap(t *testing.T) {
- tc, tracker := setupTestClient(t)
- seed := testutil.GetSeedData()
- // Phase 1: Create Permissions (skip if already bootstrapped)
- t.Run("CreatePermissions", func(t *testing.T) {
- for _, perm := range seed.Permissions {
- // Skip if already exists from bootstrap
- if _, exists := tracker.Permissions[perm.Code]; exists {
- continue
- }
- var response struct {
- CreatePermission struct {
- ID string `json:"id"`
- Code string `json:"code"`
- Description string `json:"description"`
- } `json:"createPermission"`
- }
- query := fmt.Sprintf(`mutation { createPermission(input: {code: "%s", description: "%s"}) { id code description } }`, perm.Code, perm.Description)
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create permission %s: %v", perm.Code, err)
- }
- tracker.Permissions[perm.Code] = response.CreatePermission.ID
- }
- var permsResponse struct {
- Permissions []interface{} `json:"permissions"`
- }
- tc.client.Post(`query { permissions { id code description } }`, &permsResponse)
- jsonBytes, _ := json.MarshalIndent(permsResponse, "", " ")
- snapshotResult(t, "permissions", string(jsonBytes))
- })
- // Phase 2: Create Roles (skip if already bootstrapped)
- t.Run("CreateRoles", func(t *testing.T) {
- for _, role := range seed.Roles {
- // Skip if already exists from bootstrap
- if _, exists := tracker.Roles[role.Name]; exists {
- continue
- }
- permIDs := make([]string, len(role.PermissionCodes))
- for i, code := range role.PermissionCodes {
- permIDs[i] = tracker.Permissions[code]
- }
- var response struct {
- CreateRole struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Description string `json:"description"`
- } `json:"createRole"`
- }
- query := fmt.Sprintf(`mutation { createRole(input: {name: "%s", description: "%s", permissions: ["%s"]}) { id name description } }`, role.Name, role.Description, strings.Join(permIDs, `", "`))
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create role %s: %v", role.Name, err)
- }
- tracker.Roles[role.Name] = response.CreateRole.ID
- }
- var rolesResponse struct {
- Roles []interface{} `json:"roles"`
- }
- tc.client.Post(`query { roles { id name description permissions { id code } } }`, &rolesResponse)
- jsonBytes, _ := json.MarshalIndent(rolesResponse, "", " ")
- snapshotResult(t, "roles", string(jsonBytes))
- })
- // Phase 3: Create Users (additional to bootstrapped admin)
- t.Run("CreateUsers", func(t *testing.T) {
- for _, user := range seed.Users {
- roleIDs := make([]string, len(user.RoleNames))
- for i, name := range user.RoleNames {
- roleIDs[i] = tracker.Roles[name]
- }
- var response struct {
- CreateUser struct {
- ID string `json:"id"`
- Email string `json:"email"`
- } `json:"createUser"`
- }
- query := fmt.Sprintf(`mutation { createUser(input: {email: "%s", password: "%s", roles: ["%s"]}) { id email } }`, user.Email, user.Password, strings.Join(roleIDs, `", "`))
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create user %s: %v", user.Email, err)
- }
- tracker.Users[user.Email] = response.CreateUser.ID
- }
- var usersResponse struct {
- Users []interface{} `json:"users"`
- }
- tc.client.Post(`query { users { id email roles { id name } } }`, &usersResponse)
- jsonBytes, _ := json.MarshalIndent(usersResponse, "", " ")
- snapshotResult(t, "users", string(jsonBytes))
- })
- // Phase 4: Create Task Statuses (skip if already bootstrapped)
- t.Run("CreateTaskStatuses", func(t *testing.T) {
- for _, status := range seed.TaskStatuses {
- // Skip if already exists from bootstrap
- if _, exists := tracker.TaskStatuses[status.Code]; exists {
- continue
- }
- var response struct {
- CreateTaskStatus struct {
- ID string `json:"id"`
- Code string `json:"code"`
- Label string `json:"label"`
- } `json:"createTaskStatus"`
- }
- query := fmt.Sprintf(`mutation { createTaskStatus(input: {code: "%s", label: "%s"}) { id code label } }`, status.Code, status.Label)
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create task status %s: %v", status.Code, err)
- }
- tracker.TaskStatuses[status.Code] = response.CreateTaskStatus.ID
- }
- var statusesResponse struct {
- TaskStatuses []interface{} `json:"taskStatuses"`
- }
- tc.client.Post(`query { taskStatuses { id code label } }`, &statusesResponse)
- jsonBytes, _ := json.MarshalIndent(statusesResponse, "", " ")
- snapshotResult(t, "taskStatuses", string(jsonBytes))
- })
- // Phase 5: Create Services
- t.Run("CreateServices", func(t *testing.T) {
- for _, service := range seed.Services {
- participantIDs := make([]string, len(service.ParticipantEmails))
- for i, email := range service.ParticipantEmails {
- participantIDs[i] = tracker.Users[email]
- }
- var response struct {
- CreateService struct {
- ID string `json:"id"`
- Name string `json:"name"`
- } `json:"createService"`
- }
- query := fmt.Sprintf(`mutation { createService(input: {name: "%s", description: "%s", createdById: "%s", participants: ["%s"]}) { id name } }`, service.Name, service.Description, tracker.Users[service.CreatorEmail], strings.Join(participantIDs, `", "`))
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create service %s: %v", service.Name, err)
- }
- tracker.Services[service.Name] = response.CreateService.ID
- }
- var servicesResponse struct {
- Services []interface{} `json:"services"`
- }
- tc.client.Post(`query { services { id name description } }`, &servicesResponse)
- jsonBytes, _ := json.MarshalIndent(servicesResponse, "", " ")
- snapshotResult(t, "services", string(jsonBytes))
- })
- // Phase 6: Create Tasks
- t.Run("CreateTasks", func(t *testing.T) {
- for _, task := range seed.Tasks {
- var response struct {
- CreateTask struct {
- ID string `json:"id"`
- Title string `json:"title"`
- } `json:"createTask"`
- }
- var assigneeID string
- if task.AssigneeEmail != "" {
- assigneeID = tracker.Users[task.AssigneeEmail]
- }
- statusID := tracker.TaskStatuses[task.StatusCode]
- if assigneeID != "" {
- query := fmt.Sprintf(`mutation { createTask(input: {title: "%s", content: "%s", createdById: "%s", assigneeId: "%s", statusId: "%s", priority: "%s"}) { id title } }`, task.Title, task.Content, tracker.Users[task.CreatorEmail], assigneeID, statusID, task.Priority)
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create task %s: %v", task.Title, err)
- }
- } else {
- query := fmt.Sprintf(`mutation { createTask(input: {title: "%s", content: "%s", createdById: "%s", statusId: "%s", priority: "%s"}) { id title } }`, task.Title, task.Content, tracker.Users[task.CreatorEmail], statusID, task.Priority)
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create task %s: %v", task.Title, err)
- }
- }
- tracker.Tasks[task.Title] = response.CreateTask.ID
- }
- var tasksResponse struct {
- Tasks []interface{} `json:"tasks"`
- }
- tc.client.Post(`query { tasks { id title content priority } }`, &tasksResponse)
- jsonBytes, _ := json.MarshalIndent(tasksResponse, "", " ")
- snapshotResult(t, "tasks", string(jsonBytes))
- })
- // Phase 7: Create Notes
- t.Run("CreateNotes", func(t *testing.T) {
- for _, note := range seed.Notes {
- var response struct {
- CreateNote struct {
- ID string `json:"id"`
- Title string `json:"title"`
- } `json:"createNote"`
- }
- query := fmt.Sprintf(`mutation { createNote(input: {title: "%s", content: "%s", userId: "%s", serviceId: "%s"}) { id title } }`, note.Title, note.Content, tracker.Users[note.UserEmail], tracker.Services[note.ServiceName])
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create note %s: %v", note.Title, err)
- }
- tracker.Notes[note.Title] = response.CreateNote.ID
- }
- var notesResponse struct {
- Notes []interface{} `json:"notes"`
- }
- tc.client.Post(`query { notes { id title content } }`, ¬esResponse)
- jsonBytes, _ := json.MarshalIndent(notesResponse, "", " ")
- snapshotResult(t, "notes", string(jsonBytes))
- })
- // Phase 8: Create Channels
- t.Run("CreateChannels", func(t *testing.T) {
- for _, channel := range seed.Channels {
- participantIDs := make([]string, len(channel.ParticipantEmails))
- for i, email := range channel.ParticipantEmails {
- participantIDs[i] = tracker.Users[email]
- }
- var response struct {
- CreateChannel struct {
- ID string `json:"id"`
- } `json:"createChannel"`
- }
- query := fmt.Sprintf(`mutation { createChannel(input: {participants: ["%s"]}) { id } }`, strings.Join(participantIDs, `", "`))
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create channel: %v", err)
- }
- tracker.Channels = append(tracker.Channels, response.CreateChannel.ID)
- }
- var channelsResponse struct {
- Channels []interface{} `json:"channels"`
- }
- tc.client.Post(`query { channels { id } }`, &channelsResponse)
- jsonBytes, _ := json.MarshalIndent(channelsResponse, "", " ")
- snapshotResult(t, "channels", string(jsonBytes))
- })
- // Phase 9: Create Messages
- t.Run("CreateMessages", func(t *testing.T) {
- for _, msg := range seed.Messages {
- var response struct {
- CreateMessage struct {
- ID string `json:"id"`
- } `json:"createMessage"`
- }
- query := fmt.Sprintf(`mutation { createMessage(input: {conversationId: "%s", senderId: "%s", content: "%s"}) { id } }`, tracker.Channels[msg.ChannelIndex], tracker.Users[msg.SenderEmail], msg.Content)
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create message: %v", err)
- }
- tracker.Messages = append(tracker.Messages, response.CreateMessage.ID)
- }
- var messagesResponse struct {
- Messages []interface{} `json:"messages"`
- }
- tc.client.Post(`query { messages { id content } }`, &messagesResponse)
- jsonBytes, _ := json.MarshalIndent(messagesResponse, "", " ")
- snapshotResult(t, "messages", string(jsonBytes))
- })
- }
- // TestIntegration_Update tests update operations
- func TestIntegration_Update(t *testing.T) {
- tc, tracker := setupTestClient(t)
- seed := testutil.GetSeedData()
- // Bootstrap first
- bootstrapData(t, tc, tracker, seed)
- // Update a user
- t.Run("UpdateUser", func(t *testing.T) {
- userID := tracker.Users["admin@example.com"]
- var response struct {
- UpdateUser struct {
- ID string `json:"id"`
- Email string `json:"email"`
- } `json:"updateUser"`
- }
- query := fmt.Sprintf(`mutation { updateUser(id: "%s", input: {email: "admin-updated@example.com", password: "new-password", roles: ["%s"]}) { id email } }`, userID, tracker.Roles["admin"])
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to update user: %v", err)
- }
- var usersResponse struct {
- Users []interface{} `json:"users"`
- }
- tc.client.Post(`query { users { id email roles { id name } } }`, &usersResponse)
- jsonBytes, _ := json.MarshalIndent(usersResponse, "", " ")
- snapshotResult(t, "users_after_update", string(jsonBytes))
- })
- // Update a task
- t.Run("UpdateTask", func(t *testing.T) {
- taskID := tracker.Tasks["Setup development environment"]
- var response struct {
- UpdateTask struct {
- ID string `json:"id"`
- Title string `json:"title"`
- } `json:"updateTask"`
- }
- query := fmt.Sprintf(`mutation { updateTask(id: "%s", input: {title: "Setup development environment - COMPLETE", priority: "low"}) { id title } }`, taskID)
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to update task: %v", err)
- }
- var tasksResponse struct {
- Tasks []interface{} `json:"tasks"`
- }
- tc.client.Post(`query { tasks { id title content priority } }`, &tasksResponse)
- jsonBytes, _ := json.MarshalIndent(tasksResponse, "", " ")
- snapshotResult(t, "tasks_after_update", string(jsonBytes))
- })
- }
- // TestIntegration_Delete tests delete operations
- func TestIntegration_Delete(t *testing.T) {
- tc, tracker := setupTestClient(t)
- seed := testutil.GetSeedData()
- // Bootstrap first
- bootstrapData(t, tc, tracker, seed)
- // Delete a note
- t.Run("DeleteNote", func(t *testing.T) {
- noteID := tracker.Notes["Meeting notes - Sprint 1"]
- var response struct {
- DeleteNote bool `json:"deleteNote"`
- }
- query := fmt.Sprintf(`mutation { deleteNote(id: "%s") }`, noteID)
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to delete note: %v", err)
- }
- var notesResponse struct {
- Notes []interface{} `json:"notes"`
- }
- tc.client.Post(`query { notes { id title content } }`, ¬esResponse)
- jsonBytes, _ := json.MarshalIndent(notesResponse, "", " ")
- snapshotResult(t, "notes_after_delete", string(jsonBytes))
- })
- // Delete a task
- t.Run("DeleteTask", func(t *testing.T) {
- taskID := tracker.Tasks["Performance optimization"]
- var response struct {
- DeleteTask bool `json:"deleteTask"`
- }
- query := fmt.Sprintf(`mutation { deleteTask(id: "%s") }`, taskID)
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to delete task: %v", err)
- }
- var tasksResponse struct {
- Tasks []interface{} `json:"tasks"`
- }
- tc.client.Post(`query { tasks { id title content priority } }`, &tasksResponse)
- jsonBytes, _ := json.MarshalIndent(tasksResponse, "", " ")
- snapshotResult(t, "tasks_after_delete", string(jsonBytes))
- })
- }
- // TestIntegration_Subscriptions tests subscription functionality
- func TestIntegration_Subscriptions(t *testing.T) {
- db, err := testutil.SetupAndBootstrapTestDB()
- if err != nil {
- t.Fatalf("Failed to setup test database: %v", err)
- }
- resolver := NewResolver(db)
- schema := NewExecutableSchema(Config{Resolvers: resolver})
- srv := handler.NewDefaultServer(schema)
- authSrv := auth.AuthMiddleware(srv)
- // Create admin client
- adminClient := client.New(authSrv)
- // Login as admin
- var loginResponse struct {
- Login struct {
- Token string `json:"token"`
- User struct {
- ID string `json:"id"`
- Email string `json:"email"`
- } `json:"user"`
- } `json:"login"`
- }
- loginQuery := `mutation { login(email: "admin@example.com", password: "secret123") { token user { id email } } }`
- err = adminClient.Post(loginQuery, &loginResponse)
- if err != nil {
- t.Fatalf("Failed to login as admin: %v", err)
- }
- adminToken := loginResponse.Login.Token
- adminID := loginResponse.Login.User.ID
- adminClient = client.New(authSrv, client.AddHeader("Authorization", "Bearer "+adminToken))
- // Create user1
- var createUserResponse struct {
- CreateUser struct {
- ID string `json:"id"`
- Email string `json:"email"`
- } `json:"createUser"`
- }
- // Get user role ID
- var rolesResponse struct {
- Roles []struct {
- ID string `json:"id"`
- Name string `json:"name"`
- } `json:"roles"`
- }
- adminClient.Post(`query { roles { id name } }`, &rolesResponse)
- var userRoleID string
- for _, role := range rolesResponse.Roles {
- if role.Name == "user" {
- userRoleID = role.ID
- break
- }
- }
- createUserQuery := fmt.Sprintf(`mutation { createUser(input: {email: "user1@example.com", password: "password123", roles: ["%s"]}) { id email } }`, userRoleID)
- err = adminClient.Post(createUserQuery, &createUserResponse)
- if err != nil {
- t.Fatalf("Failed to create user1: %v", err)
- }
- user1ID := createUserResponse.CreateUser.ID
- // Login as user1
- var user1LoginResponse struct {
- Login struct {
- Token string `json:"token"`
- } `json:"login"`
- }
- user1LoginQuery := `mutation { login(email: "user1@example.com", password: "password123") { token } }`
- err = adminClient.Post(user1LoginQuery, &user1LoginResponse)
- if err != nil {
- t.Fatalf("Failed to login as user1: %v", err)
- }
- user1Token := user1LoginResponse.Login.Token
- // Create WebSocket client for user1
- user1WSClient := client.New(authSrv, client.AddHeader("Authorization", "Bearer "+user1Token))
- // Subscribe to taskCreated as user1
- taskCreatedSub := `subscription { taskCreated { id title content assigneeId } }`
- taskCreatedChan := make(chan *model.Task, 1)
- taskCreatedSubscription := user1WSClient.Websocket(taskCreatedSub)
- defer taskCreatedSubscription.Close()
- go func() {
- for {
- var taskCreatedResponse struct {
- TaskCreated *model.Task `json:"taskCreated"`
- }
- err := taskCreatedSubscription.Next(&taskCreatedResponse)
- if err != nil {
- return
- }
- if taskCreatedResponse.TaskCreated != nil {
- select {
- case taskCreatedChan <- taskCreatedResponse.TaskCreated:
- default:
- }
- }
- }
- }()
- // Subscribe to messageAdded as user1
- messageAddedSub := `subscription { messageAdded { id content conversationId senderId } }`
- messageAddedChan := make(chan *model.Message, 1)
- messageAddedSubscription := user1WSClient.Websocket(messageAddedSub)
- defer messageAddedSubscription.Close()
- go func() {
- for {
- var messageAddedResponse struct {
- MessageAdded *model.Message `json:"messageAdded"`
- }
- err := messageAddedSubscription.Next(&messageAddedResponse)
- if err != nil {
- return
- }
- if messageAddedResponse.MessageAdded != nil {
- select {
- case messageAddedChan <- messageAddedResponse.MessageAdded:
- default:
- }
- }
- }
- }()
- // Give subscriptions time to connect
- time.Sleep(100 * time.Millisecond)
- // Create TestConversation with admin and user1 as participants
- var createChannelResponse struct {
- CreateChannel struct {
- ID string `json:"id"`
- } `json:"createChannel"`
- }
- createChannelQuery := fmt.Sprintf(`mutation { createChannel(input: {participants: ["%s", "%s"]}) { id } }`, adminID, user1ID)
- err = adminClient.Post(createChannelQuery, &createChannelResponse)
- if err != nil {
- t.Fatalf("Failed to create channel: %v", err)
- }
- channelID := createChannelResponse.CreateChannel.ID
- // Admin sends a message to the channel
- var createMessageResponse struct {
- CreateMessage struct {
- ID string `json:"id"`
- } `json:"createMessage"`
- }
- createMessageQuery := fmt.Sprintf(`mutation { createMessage(input: {conversationId: "%s", senderId: "%s", content: "Hello user1!"}) { id } }`, channelID, adminID)
- err = adminClient.Post(createMessageQuery, &createMessageResponse)
- if err != nil {
- t.Fatalf("Failed to create message: %v", err)
- }
- // Wait for messageAdded event
- select {
- case msg := <-messageAddedChan:
- if msg == nil {
- t.Error("Expected messageAdded event, got nil")
- } else if msg.Content != "Hello user1!" {
- t.Errorf("Expected message content 'Hello user1!', got '%s'", msg.Content)
- } else {
- t.Log("user1 received messageAdded event successfully")
- }
- case <-time.After(2 * time.Second):
- t.Error("Timeout waiting for messageAdded event")
- }
- // Get a task status ID
- var statusesResponse struct {
- TaskStatuses []struct {
- ID string `json:"id"`
- Code string `json:"code"`
- } `json:"taskStatuses"`
- }
- adminClient.Post(`query { taskStatuses { id code } }`, &statusesResponse)
- var statusID string
- if len(statusesResponse.TaskStatuses) > 0 {
- statusID = statusesResponse.TaskStatuses[0].ID
- }
- // Admin creates a task assigned to user1
- var createTaskResponse struct {
- CreateTask struct {
- ID string `json:"id"`
- Title string `json:"title"`
- } `json:"createTask"`
- }
- createTaskQuery := fmt.Sprintf(`mutation { createTask(input: {title: "Task for user1", content: "This is assigned to user1", createdById: "%s", assigneeId: "%s", statusId: "%s", priority: "medium"}) { id title } }`, adminID, user1ID, statusID)
- err = adminClient.Post(createTaskQuery, &createTaskResponse)
- if err != nil {
- t.Fatalf("Failed to create task: %v", err)
- }
- // Wait for taskCreated event
- select {
- case task := <-taskCreatedChan:
- if task == nil {
- t.Error("Expected taskCreated event, got nil")
- } else if task.Title != "Task for user1" {
- t.Errorf("Expected task title 'Task for user1', got '%s'", task.Title)
- } else {
- t.Log("user1 received taskCreated event successfully")
- }
- case <-time.After(2 * time.Second):
- t.Error("Timeout waiting for taskCreated event")
- }
- }
- // bootstrapData creates all entities for testing (skips existing items)
- func bootstrapData(t *testing.T, tc *TestClient, tracker *IDTracker, seed testutil.SeedData) {
- // Create Permissions (skip if already exists)
- for _, perm := range seed.Permissions {
- if _, exists := tracker.Permissions[perm.Code]; exists {
- continue
- }
- var response struct {
- CreatePermission struct {
- ID string `json:"id"`
- } `json:"createPermission"`
- }
- query := fmt.Sprintf(`mutation { createPermission(input: {code: "%s", description: "%s"}) { id } }`, perm.Code, perm.Description)
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create permission %s: %v", perm.Code, err)
- }
- tracker.Permissions[perm.Code] = response.CreatePermission.ID
- }
- // Create Roles (skip if already exists)
- for _, role := range seed.Roles {
- if _, exists := tracker.Roles[role.Name]; exists {
- continue
- }
- permIDs := make([]string, len(role.PermissionCodes))
- for i, code := range role.PermissionCodes {
- permIDs[i] = tracker.Permissions[code]
- }
- var response struct {
- CreateRole struct {
- ID string `json:"id"`
- } `json:"createRole"`
- }
- query := fmt.Sprintf(`mutation { createRole(input: {name: "%s", description: "%s", permissions: ["%s"]}) { id } }`, role.Name, role.Description, strings.Join(permIDs, `", "`))
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create role %s: %v", role.Name, err)
- }
- tracker.Roles[role.Name] = response.CreateRole.ID
- }
- // Create Users (skip if already exists)
- for _, user := range seed.Users {
- if _, exists := tracker.Users[user.Email]; exists {
- continue
- }
- roleIDs := make([]string, len(user.RoleNames))
- for i, name := range user.RoleNames {
- roleIDs[i] = tracker.Roles[name]
- }
- var response struct {
- CreateUser struct {
- ID string `json:"id"`
- } `json:"createUser"`
- }
- query := fmt.Sprintf(`mutation { createUser(input: {email: "%s", password: "%s", roles: ["%s"]}) { id } }`, user.Email, user.Password, strings.Join(roleIDs, `", "`))
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create user %s: %v", user.Email, err)
- }
- tracker.Users[user.Email] = response.CreateUser.ID
- }
- // Create Task Statuses (skip if already exists)
- for _, status := range seed.TaskStatuses {
- if _, exists := tracker.TaskStatuses[status.Code]; exists {
- continue
- }
- var response struct {
- CreateTaskStatus struct {
- ID string `json:"id"`
- } `json:"createTaskStatus"`
- }
- query := fmt.Sprintf(`mutation { createTaskStatus(input: {code: "%s", label: "%s"}) { id } }`, status.Code, status.Label)
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create task status %s: %v", status.Code, err)
- }
- tracker.TaskStatuses[status.Code] = response.CreateTaskStatus.ID
- }
- // Create Services
- for _, service := range seed.Services {
- participantIDs := make([]string, len(service.ParticipantEmails))
- for i, email := range service.ParticipantEmails {
- participantIDs[i] = tracker.Users[email]
- }
- var response struct {
- CreateService struct {
- ID string `json:"id"`
- } `json:"createService"`
- }
- query := fmt.Sprintf(`mutation { createService(input: {name: "%s", description: "%s", createdById: "%s", participants: ["%s"]}) { id } }`, service.Name, service.Description, tracker.Users[service.CreatorEmail], strings.Join(participantIDs, `", "`))
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create service %s: %v", service.Name, err)
- }
- tracker.Services[service.Name] = response.CreateService.ID
- }
- // Create Tasks
- for _, task := range seed.Tasks {
- var response struct {
- CreateTask struct {
- ID string `json:"id"`
- } `json:"createTask"`
- }
- statusID := tracker.TaskStatuses[task.StatusCode]
- if task.AssigneeEmail != "" {
- query := fmt.Sprintf(`mutation { createTask(input: {title: "%s", content: "%s", createdById: "%s", assigneeId: "%s", statusId: "%s", priority: "%s"}) { id } }`, task.Title, task.Content, tracker.Users[task.CreatorEmail], tracker.Users[task.AssigneeEmail], statusID, task.Priority)
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create task %s: %v", task.Title, err)
- }
- } else {
- query := fmt.Sprintf(`mutation { createTask(input: {title: "%s", content: "%s", createdById: "%s", statusId: "%s", priority: "%s"}) { id } }`, task.Title, task.Content, tracker.Users[task.CreatorEmail], statusID, task.Priority)
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create task %s: %v", task.Title, err)
- }
- }
- tracker.Tasks[task.Title] = response.CreateTask.ID
- }
- // Create Notes
- for _, note := range seed.Notes {
- var response struct {
- CreateNote struct {
- ID string `json:"id"`
- } `json:"createNote"`
- }
- query := fmt.Sprintf(`mutation { createNote(input: {title: "%s", content: "%s", userId: "%s", serviceId: "%s"}) { id } }`, note.Title, note.Content, tracker.Users[note.UserEmail], tracker.Services[note.ServiceName])
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create note %s: %v", note.Title, err)
- }
- tracker.Notes[note.Title] = response.CreateNote.ID
- }
- // Create Channels
- for _, channel := range seed.Channels {
- participantIDs := make([]string, len(channel.ParticipantEmails))
- for i, email := range channel.ParticipantEmails {
- participantIDs[i] = tracker.Users[email]
- }
- var response struct {
- CreateChannel struct {
- ID string `json:"id"`
- } `json:"createChannel"`
- }
- query := fmt.Sprintf(`mutation { createChannel(input: {participants: ["%s"]}) { id } }`, strings.Join(participantIDs, `", "`))
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create channel: %v", err)
- }
- tracker.Channels = append(tracker.Channels, response.CreateChannel.ID)
- }
- // Create Messages
- for _, msg := range seed.Messages {
- var response struct {
- CreateMessage struct {
- ID string `json:"id"`
- } `json:"createMessage"`
- }
- query := fmt.Sprintf(`mutation { createMessage(input: {conversationId: "%s", senderId: "%s", content: "%s"}) { id } }`, tracker.Channels[msg.ChannelIndex], tracker.Users[msg.SenderEmail], msg.Content)
- err := tc.client.Post(query, &response)
- if err != nil {
- t.Fatalf("Failed to create message: %v", err)
- }
- tracker.Messages = append(tracker.Messages, response.CreateMessage.ID)
- }
- }
|