1
0

integration_test.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  1. package graph
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "strings"
  6. "testing"
  7. "time"
  8. "github.com/99designs/gqlgen/client"
  9. "github.com/99designs/gqlgen/graphql/handler"
  10. "github.com/bradleyjkemp/cupaloy/v2"
  11. "gogs.dmsc.dev/arp/auth"
  12. "gogs.dmsc.dev/arp/graph/model"
  13. "gogs.dmsc.dev/arp/graph/testutil"
  14. "gorm.io/gorm"
  15. )
  16. var snapshotter = cupaloy.New(cupaloy.SnapshotSubdirectory("testdata/snapshots"))
  17. type TestClient struct {
  18. client *client.Client
  19. db *gorm.DB
  20. token string
  21. }
  22. type IDTracker struct {
  23. Permissions map[string]string
  24. Roles map[string]string
  25. Users map[string]string
  26. TaskStatuses map[string]string
  27. Services map[string]string
  28. Tasks map[string]string
  29. Notes map[string]string
  30. Messages []string
  31. }
  32. func NewIDTracker() *IDTracker {
  33. return &IDTracker{
  34. Permissions: make(map[string]string),
  35. Roles: make(map[string]string),
  36. Users: make(map[string]string),
  37. TaskStatuses: make(map[string]string),
  38. Services: make(map[string]string),
  39. Tasks: make(map[string]string),
  40. Notes: make(map[string]string),
  41. Messages: make([]string, 0),
  42. }
  43. }
  44. func setupTestClient(t *testing.T) (*TestClient, *IDTracker) {
  45. // Setup and bootstrap the database with initial data from init_tests.sql
  46. db, err := testutil.SetupAndBootstrapTestDB()
  47. if err != nil {
  48. t.Fatalf("Failed to setup test database: %v", err)
  49. }
  50. resolver := &Resolver{DB: db}
  51. schema := NewExecutableSchema(Config{Resolvers: resolver})
  52. srv := handler.NewDefaultServer(schema)
  53. // Wrap with auth middleware
  54. authSrv := auth.AuthMiddleware(srv)
  55. gqlClient := client.New(authSrv)
  56. // Login as admin to get a token
  57. var loginResponse struct {
  58. Login struct {
  59. Token string `json:"token"`
  60. User struct {
  61. ID string `json:"id"`
  62. Email string `json:"email"`
  63. } `json:"user"`
  64. } `json:"login"`
  65. }
  66. loginQuery := `mutation { login(email: "admin@example.com", password: "secret123") { token user { id email } } }`
  67. err = gqlClient.Post(loginQuery, &loginResponse)
  68. if err != nil {
  69. t.Fatalf("Failed to login as admin: %v", err)
  70. }
  71. if loginResponse.Login.Token == "" {
  72. t.Fatal("Login returned empty token")
  73. }
  74. // Create authenticated client
  75. authClient := client.New(authSrv, client.AddHeader("Authorization", "Bearer "+loginResponse.Login.Token))
  76. // Initialize tracker with bootstrapped data
  77. tracker := NewIDTracker()
  78. // Track bootstrapped admin user
  79. tracker.Users["admin@example.com"] = loginResponse.Login.User.ID
  80. // Fetch and track bootstrapped permissions
  81. var permsResponse struct {
  82. Permissions []struct {
  83. ID string `json:"id"`
  84. Code string `json:"code"`
  85. } `json:"permissions"`
  86. }
  87. authClient.Post(`query { permissions { id code } }`, &permsResponse)
  88. for _, perm := range permsResponse.Permissions {
  89. tracker.Permissions[perm.Code] = perm.ID
  90. }
  91. // Fetch and track bootstrapped roles
  92. var rolesResponse struct {
  93. Roles []struct {
  94. ID string `json:"id"`
  95. Name string `json:"name"`
  96. } `json:"roles"`
  97. }
  98. authClient.Post(`query { roles { id name } }`, &rolesResponse)
  99. for _, role := range rolesResponse.Roles {
  100. tracker.Roles[role.Name] = role.ID
  101. }
  102. // Fetch and track bootstrapped task statuses
  103. var statusesResponse struct {
  104. TaskStatuses []struct {
  105. ID string `json:"id"`
  106. Code string `json:"code"`
  107. } `json:"taskStatuses"`
  108. }
  109. authClient.Post(`query { taskStatuses { id code } }`, &statusesResponse)
  110. for _, status := range statusesResponse.TaskStatuses {
  111. tracker.TaskStatuses[status.Code] = status.ID
  112. }
  113. return &TestClient{client: authClient, db: db, token: loginResponse.Login.Token}, tracker
  114. }
  115. func normalizeJSON(jsonStr string) string {
  116. var data interface{}
  117. if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
  118. return jsonStr
  119. }
  120. normalizeData(data)
  121. bytes, _ := json.MarshalIndent(data, "", " ")
  122. return string(bytes)
  123. }
  124. func normalizeData(data interface{}) {
  125. switch v := data.(type) {
  126. case map[string]interface{}:
  127. delete(v, "id")
  128. delete(v, "ID")
  129. delete(v, "createdAt")
  130. delete(v, "updatedAt")
  131. delete(v, "sentAt")
  132. delete(v, "createdByID")
  133. delete(v, "userId")
  134. delete(v, "serviceId")
  135. delete(v, "statusId")
  136. delete(v, "assigneeId")
  137. delete(v, "conversationId")
  138. delete(v, "senderId")
  139. for _, val := range v {
  140. normalizeData(val)
  141. }
  142. case []interface{}:
  143. for _, item := range v {
  144. normalizeData(item)
  145. }
  146. }
  147. }
  148. func snapshotResult(t *testing.T, name string, jsonStr string) {
  149. normalized := normalizeJSON(jsonStr)
  150. snapshotter.SnapshotT(t, name, normalized)
  151. }
  152. func TestIntegration_Bootstrap(t *testing.T) {
  153. tc, tracker := setupTestClient(t)
  154. seed := testutil.GetSeedData()
  155. // Phase 1: Create Permissions (skip if already bootstrapped)
  156. t.Run("CreatePermissions", func(t *testing.T) {
  157. for _, perm := range seed.Permissions {
  158. // Skip if already exists from bootstrap
  159. if _, exists := tracker.Permissions[perm.Code]; exists {
  160. continue
  161. }
  162. var response struct {
  163. CreatePermission struct {
  164. ID string `json:"id"`
  165. Code string `json:"code"`
  166. Description string `json:"description"`
  167. } `json:"createPermission"`
  168. }
  169. query := fmt.Sprintf(`mutation { createPermission(input: {code: "%s", description: "%s"}) { id code description } }`, perm.Code, perm.Description)
  170. err := tc.client.Post(query, &response)
  171. if err != nil {
  172. t.Fatalf("Failed to create permission %s: %v", perm.Code, err)
  173. }
  174. tracker.Permissions[perm.Code] = response.CreatePermission.ID
  175. }
  176. var permsResponse struct {
  177. Permissions []interface{} `json:"permissions"`
  178. }
  179. tc.client.Post(`query { permissions { id code description } }`, &permsResponse)
  180. jsonBytes, _ := json.MarshalIndent(permsResponse, "", " ")
  181. snapshotResult(t, "permissions", string(jsonBytes))
  182. })
  183. // Phase 2: Create Roles (skip if already bootstrapped)
  184. t.Run("CreateRoles", func(t *testing.T) {
  185. for _, role := range seed.Roles {
  186. // Skip if already exists from bootstrap
  187. if _, exists := tracker.Roles[role.Name]; exists {
  188. continue
  189. }
  190. permIDs := make([]string, len(role.PermissionCodes))
  191. for i, code := range role.PermissionCodes {
  192. permIDs[i] = tracker.Permissions[code]
  193. }
  194. var response struct {
  195. CreateRole struct {
  196. ID string `json:"id"`
  197. Name string `json:"name"`
  198. Description string `json:"description"`
  199. } `json:"createRole"`
  200. }
  201. query := fmt.Sprintf(`mutation { createRole(input: {name: "%s", description: "%s", permissions: ["%s"]}) { id name description } }`, role.Name, role.Description, strings.Join(permIDs, `", "`))
  202. err := tc.client.Post(query, &response)
  203. if err != nil {
  204. t.Fatalf("Failed to create role %s: %v", role.Name, err)
  205. }
  206. tracker.Roles[role.Name] = response.CreateRole.ID
  207. }
  208. var rolesResponse struct {
  209. Roles []interface{} `json:"roles"`
  210. }
  211. tc.client.Post(`query { roles { id name description permissions { id code } } }`, &rolesResponse)
  212. jsonBytes, _ := json.MarshalIndent(rolesResponse, "", " ")
  213. snapshotResult(t, "roles", string(jsonBytes))
  214. })
  215. // Phase 3: Create Users (additional to bootstrapped admin)
  216. t.Run("CreateUsers", func(t *testing.T) {
  217. for _, user := range seed.Users {
  218. roleIDs := make([]string, len(user.RoleNames))
  219. for i, name := range user.RoleNames {
  220. roleIDs[i] = tracker.Roles[name]
  221. }
  222. var response struct {
  223. CreateUser struct {
  224. ID string `json:"id"`
  225. Email string `json:"email"`
  226. } `json:"createUser"`
  227. }
  228. query := fmt.Sprintf(`mutation { createUser(input: {email: "%s", password: "%s", roles: ["%s"]}) { id email } }`, user.Email, user.Password, strings.Join(roleIDs, `", "`))
  229. err := tc.client.Post(query, &response)
  230. if err != nil {
  231. t.Fatalf("Failed to create user %s: %v", user.Email, err)
  232. }
  233. tracker.Users[user.Email] = response.CreateUser.ID
  234. }
  235. var usersResponse struct {
  236. Users []interface{} `json:"users"`
  237. }
  238. tc.client.Post(`query { users { id email roles { id name } } }`, &usersResponse)
  239. jsonBytes, _ := json.MarshalIndent(usersResponse, "", " ")
  240. snapshotResult(t, "users", string(jsonBytes))
  241. })
  242. // Phase 4: Create Task Statuses (skip if already bootstrapped)
  243. t.Run("CreateTaskStatuses", func(t *testing.T) {
  244. for _, status := range seed.TaskStatuses {
  245. // Skip if already exists from bootstrap
  246. if _, exists := tracker.TaskStatuses[status.Code]; exists {
  247. continue
  248. }
  249. var response struct {
  250. CreateTaskStatus struct {
  251. ID string `json:"id"`
  252. Code string `json:"code"`
  253. Label string `json:"label"`
  254. } `json:"createTaskStatus"`
  255. }
  256. query := fmt.Sprintf(`mutation { createTaskStatus(input: {code: "%s", label: "%s"}) { id code label } }`, status.Code, status.Label)
  257. err := tc.client.Post(query, &response)
  258. if err != nil {
  259. t.Fatalf("Failed to create task status %s: %v", status.Code, err)
  260. }
  261. tracker.TaskStatuses[status.Code] = response.CreateTaskStatus.ID
  262. }
  263. var statusesResponse struct {
  264. TaskStatuses []interface{} `json:"taskStatuses"`
  265. }
  266. tc.client.Post(`query { taskStatuses { id code label } }`, &statusesResponse)
  267. jsonBytes, _ := json.MarshalIndent(statusesResponse, "", " ")
  268. snapshotResult(t, "taskStatuses", string(jsonBytes))
  269. })
  270. // Phase 5: Create Services
  271. t.Run("CreateServices", func(t *testing.T) {
  272. for _, service := range seed.Services {
  273. participantIDs := make([]string, len(service.ParticipantEmails))
  274. for i, email := range service.ParticipantEmails {
  275. participantIDs[i] = tracker.Users[email]
  276. }
  277. var response struct {
  278. CreateService struct {
  279. ID string `json:"id"`
  280. Name string `json:"name"`
  281. } `json:"createService"`
  282. }
  283. 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, `", "`))
  284. err := tc.client.Post(query, &response)
  285. if err != nil {
  286. t.Fatalf("Failed to create service %s: %v", service.Name, err)
  287. }
  288. tracker.Services[service.Name] = response.CreateService.ID
  289. }
  290. var servicesResponse struct {
  291. Services []interface{} `json:"services"`
  292. }
  293. tc.client.Post(`query { services { id name description } }`, &servicesResponse)
  294. jsonBytes, _ := json.MarshalIndent(servicesResponse, "", " ")
  295. snapshotResult(t, "services", string(jsonBytes))
  296. })
  297. // Phase 6: Create Tasks
  298. t.Run("CreateTasks", func(t *testing.T) {
  299. for _, task := range seed.Tasks {
  300. var response struct {
  301. CreateTask struct {
  302. ID string `json:"id"`
  303. Title string `json:"title"`
  304. } `json:"createTask"`
  305. }
  306. var assigneeID string
  307. if task.AssigneeEmail != "" {
  308. assigneeID = tracker.Users[task.AssigneeEmail]
  309. }
  310. statusID := tracker.TaskStatuses[task.StatusCode]
  311. if assigneeID != "" {
  312. 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)
  313. err := tc.client.Post(query, &response)
  314. if err != nil {
  315. t.Fatalf("Failed to create task %s: %v", task.Title, err)
  316. }
  317. } else {
  318. 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)
  319. err := tc.client.Post(query, &response)
  320. if err != nil {
  321. t.Fatalf("Failed to create task %s: %v", task.Title, err)
  322. }
  323. }
  324. tracker.Tasks[task.Title] = response.CreateTask.ID
  325. }
  326. var tasksResponse struct {
  327. Tasks []interface{} `json:"tasks"`
  328. }
  329. tc.client.Post(`query { tasks { id title content priority } }`, &tasksResponse)
  330. jsonBytes, _ := json.MarshalIndent(tasksResponse, "", " ")
  331. snapshotResult(t, "tasks", string(jsonBytes))
  332. })
  333. // Phase 7: Create Notes
  334. t.Run("CreateNotes", func(t *testing.T) {
  335. for _, note := range seed.Notes {
  336. var response struct {
  337. CreateNote struct {
  338. ID string `json:"id"`
  339. Title string `json:"title"`
  340. } `json:"createNote"`
  341. }
  342. 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])
  343. err := tc.client.Post(query, &response)
  344. if err != nil {
  345. t.Fatalf("Failed to create note %s: %v", note.Title, err)
  346. }
  347. tracker.Notes[note.Title] = response.CreateNote.ID
  348. }
  349. var notesResponse struct {
  350. Notes []interface{} `json:"notes"`
  351. }
  352. tc.client.Post(`query { notes { id title content } }`, &notesResponse)
  353. jsonBytes, _ := json.MarshalIndent(notesResponse, "", " ")
  354. snapshotResult(t, "notes", string(jsonBytes))
  355. })
  356. // Phase 8: Create Messages
  357. t.Run("CreateMessages", func(t *testing.T) {
  358. for _, msg := range seed.Messages {
  359. receiverIDs := make([]string, len(msg.ReceiverEmails))
  360. for i, email := range msg.ReceiverEmails {
  361. receiverIDs[i] = tracker.Users[email]
  362. }
  363. var response struct {
  364. CreateMessage struct {
  365. ID string `json:"id"`
  366. } `json:"createMessage"`
  367. }
  368. query := fmt.Sprintf(`mutation { createMessage(input: {content: "%s", receivers: ["%s"]}) { id } }`, msg.Content, strings.Join(receiverIDs, `", "`))
  369. err := tc.client.Post(query, &response)
  370. if err != nil {
  371. t.Fatalf("Failed to create message: %v", err)
  372. }
  373. tracker.Messages = append(tracker.Messages, response.CreateMessage.ID)
  374. }
  375. var messagesResponse struct {
  376. Messages []interface{} `json:"messages"`
  377. }
  378. tc.client.Post(`query { messages { id content receivers } }`, &messagesResponse)
  379. jsonBytes, _ := json.MarshalIndent(messagesResponse, "", " ")
  380. snapshotResult(t, "messages", string(jsonBytes))
  381. })
  382. }
  383. // TestIntegration_Update tests update operations
  384. func TestIntegration_Update(t *testing.T) {
  385. tc, tracker := setupTestClient(t)
  386. seed := testutil.GetSeedData()
  387. // Bootstrap first
  388. bootstrapData(t, tc, tracker, seed)
  389. // Update a user
  390. t.Run("UpdateUser", func(t *testing.T) {
  391. userID := tracker.Users["admin@example.com"]
  392. var response struct {
  393. UpdateUser struct {
  394. ID string `json:"id"`
  395. Email string `json:"email"`
  396. } `json:"updateUser"`
  397. }
  398. query := fmt.Sprintf(`mutation { updateUser(id: "%s", input: {email: "admin-updated@example.com", password: "new-password", roles: ["%s"]}) { id email } }`, userID, tracker.Roles["admin"])
  399. err := tc.client.Post(query, &response)
  400. if err != nil {
  401. t.Fatalf("Failed to update user: %v", err)
  402. }
  403. var usersResponse struct {
  404. Users []interface{} `json:"users"`
  405. }
  406. tc.client.Post(`query { users { id email roles { id name } } }`, &usersResponse)
  407. jsonBytes, _ := json.MarshalIndent(usersResponse, "", " ")
  408. snapshotResult(t, "users_after_update", string(jsonBytes))
  409. })
  410. // Update a task
  411. t.Run("UpdateTask", func(t *testing.T) {
  412. taskID := tracker.Tasks["Setup development environment"]
  413. var response struct {
  414. UpdateTask struct {
  415. ID string `json:"id"`
  416. Title string `json:"title"`
  417. } `json:"updateTask"`
  418. }
  419. query := fmt.Sprintf(`mutation { updateTask(id: "%s", input: {title: "Setup development environment - COMPLETE", priority: "low"}) { id title } }`, taskID)
  420. err := tc.client.Post(query, &response)
  421. if err != nil {
  422. t.Fatalf("Failed to update task: %v", err)
  423. }
  424. var tasksResponse struct {
  425. Tasks []interface{} `json:"tasks"`
  426. }
  427. tc.client.Post(`query { tasks { id title content priority } }`, &tasksResponse)
  428. jsonBytes, _ := json.MarshalIndent(tasksResponse, "", " ")
  429. snapshotResult(t, "tasks_after_update", string(jsonBytes))
  430. })
  431. }
  432. // TestIntegration_Delete tests delete operations
  433. func TestIntegration_Delete(t *testing.T) {
  434. tc, tracker := setupTestClient(t)
  435. seed := testutil.GetSeedData()
  436. // Bootstrap first
  437. bootstrapData(t, tc, tracker, seed)
  438. // Delete a note
  439. t.Run("DeleteNote", func(t *testing.T) {
  440. noteID := tracker.Notes["Meeting notes - Sprint 1"]
  441. var response struct {
  442. DeleteNote bool `json:"deleteNote"`
  443. }
  444. query := fmt.Sprintf(`mutation { deleteNote(id: "%s") }`, noteID)
  445. err := tc.client.Post(query, &response)
  446. if err != nil {
  447. t.Fatalf("Failed to delete note: %v", err)
  448. }
  449. var notesResponse struct {
  450. Notes []interface{} `json:"notes"`
  451. }
  452. tc.client.Post(`query { notes { id title content } }`, &notesResponse)
  453. jsonBytes, _ := json.MarshalIndent(notesResponse, "", " ")
  454. snapshotResult(t, "notes_after_delete", string(jsonBytes))
  455. })
  456. // Delete a task
  457. t.Run("DeleteTask", func(t *testing.T) {
  458. taskID := tracker.Tasks["Performance optimization"]
  459. var response struct {
  460. DeleteTask bool `json:"deleteTask"`
  461. }
  462. query := fmt.Sprintf(`mutation { deleteTask(id: "%s") }`, taskID)
  463. err := tc.client.Post(query, &response)
  464. if err != nil {
  465. t.Fatalf("Failed to delete task: %v", err)
  466. }
  467. var tasksResponse struct {
  468. Tasks []interface{} `json:"tasks"`
  469. }
  470. tc.client.Post(`query { tasks { id title content priority } }`, &tasksResponse)
  471. jsonBytes, _ := json.MarshalIndent(tasksResponse, "", " ")
  472. snapshotResult(t, "tasks_after_delete", string(jsonBytes))
  473. })
  474. }
  475. // TestIntegration_Subscriptions tests subscription functionality
  476. func TestIntegration_Subscriptions(t *testing.T) {
  477. db, err := testutil.SetupAndBootstrapTestDB()
  478. if err != nil {
  479. t.Fatalf("Failed to setup test database: %v", err)
  480. }
  481. resolver := NewResolver(db)
  482. schema := NewExecutableSchema(Config{Resolvers: resolver})
  483. srv := handler.NewDefaultServer(schema)
  484. authSrv := auth.AuthMiddleware(srv)
  485. // Create admin client
  486. adminClient := client.New(authSrv)
  487. // Login as admin
  488. var loginResponse struct {
  489. Login struct {
  490. Token string `json:"token"`
  491. User struct {
  492. ID string `json:"id"`
  493. Email string `json:"email"`
  494. } `json:"user"`
  495. } `json:"login"`
  496. }
  497. loginQuery := `mutation { login(email: "admin@example.com", password: "secret123") { token user { id email } } }`
  498. err = adminClient.Post(loginQuery, &loginResponse)
  499. if err != nil {
  500. t.Fatalf("Failed to login as admin: %v", err)
  501. }
  502. adminToken := loginResponse.Login.Token
  503. adminID := loginResponse.Login.User.ID
  504. adminClient = client.New(authSrv, client.AddHeader("Authorization", "Bearer "+adminToken))
  505. // Create user1
  506. var createUserResponse struct {
  507. CreateUser struct {
  508. ID string `json:"id"`
  509. Email string `json:"email"`
  510. } `json:"createUser"`
  511. }
  512. // Get user role ID
  513. var rolesResponse struct {
  514. Roles []struct {
  515. ID string `json:"id"`
  516. Name string `json:"name"`
  517. } `json:"roles"`
  518. }
  519. adminClient.Post(`query { roles { id name } }`, &rolesResponse)
  520. var userRoleID string
  521. for _, role := range rolesResponse.Roles {
  522. if role.Name == "user" {
  523. userRoleID = role.ID
  524. break
  525. }
  526. }
  527. createUserQuery := fmt.Sprintf(`mutation { createUser(input: {email: "user1@example.com", password: "password123", roles: ["%s"]}) { id email } }`, userRoleID)
  528. err = adminClient.Post(createUserQuery, &createUserResponse)
  529. if err != nil {
  530. t.Fatalf("Failed to create user1: %v", err)
  531. }
  532. user1ID := createUserResponse.CreateUser.ID
  533. // Login as user1
  534. var user1LoginResponse struct {
  535. Login struct {
  536. Token string `json:"token"`
  537. } `json:"login"`
  538. }
  539. user1LoginQuery := `mutation { login(email: "user1@example.com", password: "password123") { token } }`
  540. err = adminClient.Post(user1LoginQuery, &user1LoginResponse)
  541. if err != nil {
  542. t.Fatalf("Failed to login as user1: %v", err)
  543. }
  544. user1Token := user1LoginResponse.Login.Token
  545. // Create WebSocket client for user1
  546. user1WSClient := client.New(authSrv, client.AddHeader("Authorization", "Bearer "+user1Token))
  547. // Subscribe to taskCreated as user1
  548. taskCreatedSub := `subscription { taskCreated { id title content assigneeId } }`
  549. taskCreatedChan := make(chan *model.Task, 1)
  550. taskCreatedSubscription := user1WSClient.Websocket(taskCreatedSub)
  551. defer taskCreatedSubscription.Close()
  552. go func() {
  553. for {
  554. var taskCreatedResponse struct {
  555. TaskCreated *model.Task `json:"taskCreated"`
  556. }
  557. err := taskCreatedSubscription.Next(&taskCreatedResponse)
  558. if err != nil {
  559. return
  560. }
  561. if taskCreatedResponse.TaskCreated != nil {
  562. select {
  563. case taskCreatedChan <- taskCreatedResponse.TaskCreated:
  564. default:
  565. }
  566. }
  567. }
  568. }()
  569. // Subscribe to messageAdded as user1
  570. messageAddedSub := `subscription { messageAdded { id content senderId receivers } }`
  571. messageAddedChan := make(chan *model.Message, 1)
  572. messageAddedSubscription := user1WSClient.Websocket(messageAddedSub)
  573. defer messageAddedSubscription.Close()
  574. go func() {
  575. for {
  576. var messageAddedResponse struct {
  577. MessageAdded *model.Message `json:"messageAdded"`
  578. }
  579. err := messageAddedSubscription.Next(&messageAddedResponse)
  580. if err != nil {
  581. return
  582. }
  583. if messageAddedResponse.MessageAdded != nil {
  584. select {
  585. case messageAddedChan <- messageAddedResponse.MessageAdded:
  586. default:
  587. }
  588. }
  589. }
  590. }()
  591. // Give subscriptions time to connect
  592. time.Sleep(100 * time.Millisecond)
  593. // Admin sends a message to user1
  594. var createMessageResponse struct {
  595. CreateMessage struct {
  596. ID string `json:"id"`
  597. } `json:"createMessage"`
  598. }
  599. createMessageQuery := fmt.Sprintf(`mutation { createMessage(input: {content: "Hello user1!", receivers: ["%s"]}) { id } }`, user1ID)
  600. err = adminClient.Post(createMessageQuery, &createMessageResponse)
  601. if err != nil {
  602. t.Fatalf("Failed to create message: %v", err)
  603. }
  604. // Wait for messageAdded event
  605. select {
  606. case msg := <-messageAddedChan:
  607. if msg == nil {
  608. t.Error("Expected messageAdded event, got nil")
  609. } else if msg.Content != "Hello user1!" {
  610. t.Errorf("Expected message content 'Hello user1!', got '%s'", msg.Content)
  611. } else {
  612. t.Log("user1 received messageAdded event successfully")
  613. }
  614. case <-time.After(2 * time.Second):
  615. t.Error("Timeout waiting for messageAdded event")
  616. }
  617. // Get a task status ID
  618. var statusesResponse struct {
  619. TaskStatuses []struct {
  620. ID string `json:"id"`
  621. Code string `json:"code"`
  622. } `json:"taskStatuses"`
  623. }
  624. adminClient.Post(`query { taskStatuses { id code } }`, &statusesResponse)
  625. var statusID string
  626. if len(statusesResponse.TaskStatuses) > 0 {
  627. statusID = statusesResponse.TaskStatuses[0].ID
  628. }
  629. // Admin creates a task assigned to user1
  630. var createTaskResponse struct {
  631. CreateTask struct {
  632. ID string `json:"id"`
  633. Title string `json:"title"`
  634. } `json:"createTask"`
  635. }
  636. 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)
  637. err = adminClient.Post(createTaskQuery, &createTaskResponse)
  638. if err != nil {
  639. t.Fatalf("Failed to create task: %v", err)
  640. }
  641. // Wait for taskCreated event
  642. select {
  643. case task := <-taskCreatedChan:
  644. if task == nil {
  645. t.Error("Expected taskCreated event, got nil")
  646. } else if task.Title != "Task for user1" {
  647. t.Errorf("Expected task title 'Task for user1', got '%s'", task.Title)
  648. } else {
  649. t.Log("user1 received taskCreated event successfully")
  650. }
  651. case <-time.After(2 * time.Second):
  652. t.Error("Timeout waiting for taskCreated event")
  653. }
  654. }
  655. // bootstrapData creates all entities for testing (skips existing items)
  656. func bootstrapData(t *testing.T, tc *TestClient, tracker *IDTracker, seed testutil.SeedData) {
  657. // Create Permissions (skip if already exists)
  658. for _, perm := range seed.Permissions {
  659. if _, exists := tracker.Permissions[perm.Code]; exists {
  660. continue
  661. }
  662. var response struct {
  663. CreatePermission struct {
  664. ID string `json:"id"`
  665. } `json:"createPermission"`
  666. }
  667. query := fmt.Sprintf(`mutation { createPermission(input: {code: "%s", description: "%s"}) { id } }`, perm.Code, perm.Description)
  668. err := tc.client.Post(query, &response)
  669. if err != nil {
  670. t.Fatalf("Failed to create permission %s: %v", perm.Code, err)
  671. }
  672. tracker.Permissions[perm.Code] = response.CreatePermission.ID
  673. }
  674. // Create Roles (skip if already exists)
  675. for _, role := range seed.Roles {
  676. if _, exists := tracker.Roles[role.Name]; exists {
  677. continue
  678. }
  679. permIDs := make([]string, len(role.PermissionCodes))
  680. for i, code := range role.PermissionCodes {
  681. permIDs[i] = tracker.Permissions[code]
  682. }
  683. var response struct {
  684. CreateRole struct {
  685. ID string `json:"id"`
  686. } `json:"createRole"`
  687. }
  688. query := fmt.Sprintf(`mutation { createRole(input: {name: "%s", description: "%s", permissions: ["%s"]}) { id } }`, role.Name, role.Description, strings.Join(permIDs, `", "`))
  689. err := tc.client.Post(query, &response)
  690. if err != nil {
  691. t.Fatalf("Failed to create role %s: %v", role.Name, err)
  692. }
  693. tracker.Roles[role.Name] = response.CreateRole.ID
  694. }
  695. // Create Users (skip if already exists)
  696. for _, user := range seed.Users {
  697. if _, exists := tracker.Users[user.Email]; exists {
  698. continue
  699. }
  700. roleIDs := make([]string, len(user.RoleNames))
  701. for i, name := range user.RoleNames {
  702. roleIDs[i] = tracker.Roles[name]
  703. }
  704. var response struct {
  705. CreateUser struct {
  706. ID string `json:"id"`
  707. } `json:"createUser"`
  708. }
  709. query := fmt.Sprintf(`mutation { createUser(input: {email: "%s", password: "%s", roles: ["%s"]}) { id } }`, user.Email, user.Password, strings.Join(roleIDs, `", "`))
  710. err := tc.client.Post(query, &response)
  711. if err != nil {
  712. t.Fatalf("Failed to create user %s: %v", user.Email, err)
  713. }
  714. tracker.Users[user.Email] = response.CreateUser.ID
  715. }
  716. // Create Task Statuses (skip if already exists)
  717. for _, status := range seed.TaskStatuses {
  718. if _, exists := tracker.TaskStatuses[status.Code]; exists {
  719. continue
  720. }
  721. var response struct {
  722. CreateTaskStatus struct {
  723. ID string `json:"id"`
  724. } `json:"createTaskStatus"`
  725. }
  726. query := fmt.Sprintf(`mutation { createTaskStatus(input: {code: "%s", label: "%s"}) { id } }`, status.Code, status.Label)
  727. err := tc.client.Post(query, &response)
  728. if err != nil {
  729. t.Fatalf("Failed to create task status %s: %v", status.Code, err)
  730. }
  731. tracker.TaskStatuses[status.Code] = response.CreateTaskStatus.ID
  732. }
  733. // Create Services
  734. for _, service := range seed.Services {
  735. participantIDs := make([]string, len(service.ParticipantEmails))
  736. for i, email := range service.ParticipantEmails {
  737. participantIDs[i] = tracker.Users[email]
  738. }
  739. var response struct {
  740. CreateService struct {
  741. ID string `json:"id"`
  742. } `json:"createService"`
  743. }
  744. 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, `", "`))
  745. err := tc.client.Post(query, &response)
  746. if err != nil {
  747. t.Fatalf("Failed to create service %s: %v", service.Name, err)
  748. }
  749. tracker.Services[service.Name] = response.CreateService.ID
  750. }
  751. // Create Tasks
  752. for _, task := range seed.Tasks {
  753. var response struct {
  754. CreateTask struct {
  755. ID string `json:"id"`
  756. } `json:"createTask"`
  757. }
  758. statusID := tracker.TaskStatuses[task.StatusCode]
  759. if task.AssigneeEmail != "" {
  760. 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)
  761. err := tc.client.Post(query, &response)
  762. if err != nil {
  763. t.Fatalf("Failed to create task %s: %v", task.Title, err)
  764. }
  765. } else {
  766. 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)
  767. err := tc.client.Post(query, &response)
  768. if err != nil {
  769. t.Fatalf("Failed to create task %s: %v", task.Title, err)
  770. }
  771. }
  772. tracker.Tasks[task.Title] = response.CreateTask.ID
  773. }
  774. // Create Notes
  775. for _, note := range seed.Notes {
  776. var response struct {
  777. CreateNote struct {
  778. ID string `json:"id"`
  779. } `json:"createNote"`
  780. }
  781. 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])
  782. err := tc.client.Post(query, &response)
  783. if err != nil {
  784. t.Fatalf("Failed to create note %s: %v", note.Title, err)
  785. }
  786. tracker.Notes[note.Title] = response.CreateNote.ID
  787. }
  788. // Create Messages
  789. for _, msg := range seed.Messages {
  790. receiverIDs := make([]string, len(msg.ReceiverEmails))
  791. for i, email := range msg.ReceiverEmails {
  792. receiverIDs[i] = tracker.Users[email]
  793. }
  794. var response struct {
  795. CreateMessage struct {
  796. ID string `json:"id"`
  797. } `json:"createMessage"`
  798. }
  799. query := fmt.Sprintf(`mutation { createMessage(input: {content: "%s", receivers: ["%s"]}) { id } }`, msg.Content, strings.Join(receiverIDs, `", "`))
  800. err := tc.client.Post(query, &response)
  801. if err != nil {
  802. t.Fatalf("Failed to create message: %v", err)
  803. }
  804. tracker.Messages = append(tracker.Messages, response.CreateMessage.ID)
  805. }
  806. }