integration_test.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. package graph
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "strings"
  6. "testing"
  7. "github.com/99designs/gqlgen/client"
  8. "github.com/99designs/gqlgen/graphql/handler"
  9. "github.com/bradleyjkemp/cupaloy/v2"
  10. "gogs.dmsc.dev/arp/auth"
  11. "gogs.dmsc.dev/arp/graph/testutil"
  12. "gorm.io/gorm"
  13. )
  14. var snapshotter = cupaloy.New(cupaloy.SnapshotSubdirectory("testdata/snapshots"))
  15. type TestClient struct {
  16. client *client.Client
  17. db *gorm.DB
  18. token string
  19. }
  20. type IDTracker struct {
  21. Permissions map[string]string
  22. Roles map[string]string
  23. Users map[string]string
  24. TaskStatuses map[string]string
  25. Services map[string]string
  26. Tasks map[string]string
  27. Notes map[string]string
  28. Channels []string
  29. Messages []string
  30. }
  31. func NewIDTracker() *IDTracker {
  32. return &IDTracker{
  33. Permissions: make(map[string]string),
  34. Roles: make(map[string]string),
  35. Users: make(map[string]string),
  36. TaskStatuses: make(map[string]string),
  37. Services: make(map[string]string),
  38. Tasks: make(map[string]string),
  39. Notes: make(map[string]string),
  40. Channels: make([]string, 0),
  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 Channels
  357. t.Run("CreateChannels", func(t *testing.T) {
  358. for _, channel := range seed.Channels {
  359. participantIDs := make([]string, len(channel.ParticipantEmails))
  360. for i, email := range channel.ParticipantEmails {
  361. participantIDs[i] = tracker.Users[email]
  362. }
  363. var response struct {
  364. CreateChannel struct {
  365. ID string `json:"id"`
  366. } `json:"createChannel"`
  367. }
  368. query := fmt.Sprintf(`mutation { createChannel(input: {participants: ["%s"]}) { id } }`, strings.Join(participantIDs, `", "`))
  369. err := tc.client.Post(query, &response)
  370. if err != nil {
  371. t.Fatalf("Failed to create channel: %v", err)
  372. }
  373. tracker.Channels = append(tracker.Channels, response.CreateChannel.ID)
  374. }
  375. var channelsResponse struct {
  376. Channels []interface{} `json:"channels"`
  377. }
  378. tc.client.Post(`query { channels { id } }`, &channelsResponse)
  379. jsonBytes, _ := json.MarshalIndent(channelsResponse, "", " ")
  380. snapshotResult(t, "channels", string(jsonBytes))
  381. })
  382. // Phase 9: Create Messages
  383. t.Run("CreateMessages", func(t *testing.T) {
  384. for _, msg := range seed.Messages {
  385. var response struct {
  386. CreateMessage struct {
  387. ID string `json:"id"`
  388. } `json:"createMessage"`
  389. }
  390. query := fmt.Sprintf(`mutation { createMessage(input: {conversationId: "%s", senderId: "%s", content: "%s"}) { id } }`, tracker.Channels[msg.ChannelIndex], tracker.Users[msg.SenderEmail], msg.Content)
  391. err := tc.client.Post(query, &response)
  392. if err != nil {
  393. t.Fatalf("Failed to create message: %v", err)
  394. }
  395. tracker.Messages = append(tracker.Messages, response.CreateMessage.ID)
  396. }
  397. var messagesResponse struct {
  398. Messages []interface{} `json:"messages"`
  399. }
  400. tc.client.Post(`query { messages { id content } }`, &messagesResponse)
  401. jsonBytes, _ := json.MarshalIndent(messagesResponse, "", " ")
  402. snapshotResult(t, "messages", string(jsonBytes))
  403. })
  404. }
  405. // TestIntegration_Update tests update operations
  406. func TestIntegration_Update(t *testing.T) {
  407. tc, tracker := setupTestClient(t)
  408. seed := testutil.GetSeedData()
  409. // Bootstrap first
  410. bootstrapData(t, tc, tracker, seed)
  411. // Update a user
  412. t.Run("UpdateUser", func(t *testing.T) {
  413. userID := tracker.Users["admin@example.com"]
  414. var response struct {
  415. UpdateUser struct {
  416. ID string `json:"id"`
  417. Email string `json:"email"`
  418. } `json:"updateUser"`
  419. }
  420. query := fmt.Sprintf(`mutation { updateUser(id: "%s", input: {email: "admin-updated@example.com", password: "new-password", roles: ["%s"]}) { id email } }`, userID, tracker.Roles["admin"])
  421. err := tc.client.Post(query, &response)
  422. if err != nil {
  423. t.Fatalf("Failed to update user: %v", err)
  424. }
  425. var usersResponse struct {
  426. Users []interface{} `json:"users"`
  427. }
  428. tc.client.Post(`query { users { id email roles { id name } } }`, &usersResponse)
  429. jsonBytes, _ := json.MarshalIndent(usersResponse, "", " ")
  430. snapshotResult(t, "users_after_update", string(jsonBytes))
  431. })
  432. // Update a task
  433. t.Run("UpdateTask", func(t *testing.T) {
  434. taskID := tracker.Tasks["Setup development environment"]
  435. var response struct {
  436. UpdateTask struct {
  437. ID string `json:"id"`
  438. Title string `json:"title"`
  439. } `json:"updateTask"`
  440. }
  441. query := fmt.Sprintf(`mutation { updateTask(id: "%s", input: {title: "Setup development environment - COMPLETE", priority: "low"}) { id title } }`, taskID)
  442. err := tc.client.Post(query, &response)
  443. if err != nil {
  444. t.Fatalf("Failed to update task: %v", err)
  445. }
  446. var tasksResponse struct {
  447. Tasks []interface{} `json:"tasks"`
  448. }
  449. tc.client.Post(`query { tasks { id title content priority } }`, &tasksResponse)
  450. jsonBytes, _ := json.MarshalIndent(tasksResponse, "", " ")
  451. snapshotResult(t, "tasks_after_update", string(jsonBytes))
  452. })
  453. }
  454. // TestIntegration_Delete tests delete operations
  455. func TestIntegration_Delete(t *testing.T) {
  456. tc, tracker := setupTestClient(t)
  457. seed := testutil.GetSeedData()
  458. // Bootstrap first
  459. bootstrapData(t, tc, tracker, seed)
  460. // Delete a note
  461. t.Run("DeleteNote", func(t *testing.T) {
  462. noteID := tracker.Notes["Meeting notes - Sprint 1"]
  463. var response struct {
  464. DeleteNote bool `json:"deleteNote"`
  465. }
  466. query := fmt.Sprintf(`mutation { deleteNote(id: "%s") }`, noteID)
  467. err := tc.client.Post(query, &response)
  468. if err != nil {
  469. t.Fatalf("Failed to delete note: %v", err)
  470. }
  471. var notesResponse struct {
  472. Notes []interface{} `json:"notes"`
  473. }
  474. tc.client.Post(`query { notes { id title content } }`, &notesResponse)
  475. jsonBytes, _ := json.MarshalIndent(notesResponse, "", " ")
  476. snapshotResult(t, "notes_after_delete", string(jsonBytes))
  477. })
  478. // Delete a task
  479. t.Run("DeleteTask", func(t *testing.T) {
  480. taskID := tracker.Tasks["Performance optimization"]
  481. var response struct {
  482. DeleteTask bool `json:"deleteTask"`
  483. }
  484. query := fmt.Sprintf(`mutation { deleteTask(id: "%s") }`, taskID)
  485. err := tc.client.Post(query, &response)
  486. if err != nil {
  487. t.Fatalf("Failed to delete task: %v", err)
  488. }
  489. var tasksResponse struct {
  490. Tasks []interface{} `json:"tasks"`
  491. }
  492. tc.client.Post(`query { tasks { id title content priority } }`, &tasksResponse)
  493. jsonBytes, _ := json.MarshalIndent(tasksResponse, "", " ")
  494. snapshotResult(t, "tasks_after_delete", string(jsonBytes))
  495. })
  496. }
  497. // bootstrapData creates all entities for testing (skips existing items)
  498. func bootstrapData(t *testing.T, tc *TestClient, tracker *IDTracker, seed testutil.SeedData) {
  499. // Create Permissions (skip if already exists)
  500. for _, perm := range seed.Permissions {
  501. if _, exists := tracker.Permissions[perm.Code]; exists {
  502. continue
  503. }
  504. var response struct {
  505. CreatePermission struct {
  506. ID string `json:"id"`
  507. } `json:"createPermission"`
  508. }
  509. query := fmt.Sprintf(`mutation { createPermission(input: {code: "%s", description: "%s"}) { id } }`, perm.Code, perm.Description)
  510. err := tc.client.Post(query, &response)
  511. if err != nil {
  512. t.Fatalf("Failed to create permission %s: %v", perm.Code, err)
  513. }
  514. tracker.Permissions[perm.Code] = response.CreatePermission.ID
  515. }
  516. // Create Roles (skip if already exists)
  517. for _, role := range seed.Roles {
  518. if _, exists := tracker.Roles[role.Name]; exists {
  519. continue
  520. }
  521. permIDs := make([]string, len(role.PermissionCodes))
  522. for i, code := range role.PermissionCodes {
  523. permIDs[i] = tracker.Permissions[code]
  524. }
  525. var response struct {
  526. CreateRole struct {
  527. ID string `json:"id"`
  528. } `json:"createRole"`
  529. }
  530. query := fmt.Sprintf(`mutation { createRole(input: {name: "%s", description: "%s", permissions: ["%s"]}) { id } }`, role.Name, role.Description, strings.Join(permIDs, `", "`))
  531. err := tc.client.Post(query, &response)
  532. if err != nil {
  533. t.Fatalf("Failed to create role %s: %v", role.Name, err)
  534. }
  535. tracker.Roles[role.Name] = response.CreateRole.ID
  536. }
  537. // Create Users (skip if already exists)
  538. for _, user := range seed.Users {
  539. if _, exists := tracker.Users[user.Email]; exists {
  540. continue
  541. }
  542. roleIDs := make([]string, len(user.RoleNames))
  543. for i, name := range user.RoleNames {
  544. roleIDs[i] = tracker.Roles[name]
  545. }
  546. var response struct {
  547. CreateUser struct {
  548. ID string `json:"id"`
  549. } `json:"createUser"`
  550. }
  551. query := fmt.Sprintf(`mutation { createUser(input: {email: "%s", password: "%s", roles: ["%s"]}) { id } }`, user.Email, user.Password, strings.Join(roleIDs, `", "`))
  552. err := tc.client.Post(query, &response)
  553. if err != nil {
  554. t.Fatalf("Failed to create user %s: %v", user.Email, err)
  555. }
  556. tracker.Users[user.Email] = response.CreateUser.ID
  557. }
  558. // Create Task Statuses (skip if already exists)
  559. for _, status := range seed.TaskStatuses {
  560. if _, exists := tracker.TaskStatuses[status.Code]; exists {
  561. continue
  562. }
  563. var response struct {
  564. CreateTaskStatus struct {
  565. ID string `json:"id"`
  566. } `json:"createTaskStatus"`
  567. }
  568. query := fmt.Sprintf(`mutation { createTaskStatus(input: {code: "%s", label: "%s"}) { id } }`, status.Code, status.Label)
  569. err := tc.client.Post(query, &response)
  570. if err != nil {
  571. t.Fatalf("Failed to create task status %s: %v", status.Code, err)
  572. }
  573. tracker.TaskStatuses[status.Code] = response.CreateTaskStatus.ID
  574. }
  575. // Create Services
  576. for _, service := range seed.Services {
  577. participantIDs := make([]string, len(service.ParticipantEmails))
  578. for i, email := range service.ParticipantEmails {
  579. participantIDs[i] = tracker.Users[email]
  580. }
  581. var response struct {
  582. CreateService struct {
  583. ID string `json:"id"`
  584. } `json:"createService"`
  585. }
  586. 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, `", "`))
  587. err := tc.client.Post(query, &response)
  588. if err != nil {
  589. t.Fatalf("Failed to create service %s: %v", service.Name, err)
  590. }
  591. tracker.Services[service.Name] = response.CreateService.ID
  592. }
  593. // Create Tasks
  594. for _, task := range seed.Tasks {
  595. var response struct {
  596. CreateTask struct {
  597. ID string `json:"id"`
  598. } `json:"createTask"`
  599. }
  600. statusID := tracker.TaskStatuses[task.StatusCode]
  601. if task.AssigneeEmail != "" {
  602. 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)
  603. err := tc.client.Post(query, &response)
  604. if err != nil {
  605. t.Fatalf("Failed to create task %s: %v", task.Title, err)
  606. }
  607. } else {
  608. 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)
  609. err := tc.client.Post(query, &response)
  610. if err != nil {
  611. t.Fatalf("Failed to create task %s: %v", task.Title, err)
  612. }
  613. }
  614. tracker.Tasks[task.Title] = response.CreateTask.ID
  615. }
  616. // Create Notes
  617. for _, note := range seed.Notes {
  618. var response struct {
  619. CreateNote struct {
  620. ID string `json:"id"`
  621. } `json:"createNote"`
  622. }
  623. 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])
  624. err := tc.client.Post(query, &response)
  625. if err != nil {
  626. t.Fatalf("Failed to create note %s: %v", note.Title, err)
  627. }
  628. tracker.Notes[note.Title] = response.CreateNote.ID
  629. }
  630. // Create Channels
  631. for _, channel := range seed.Channels {
  632. participantIDs := make([]string, len(channel.ParticipantEmails))
  633. for i, email := range channel.ParticipantEmails {
  634. participantIDs[i] = tracker.Users[email]
  635. }
  636. var response struct {
  637. CreateChannel struct {
  638. ID string `json:"id"`
  639. } `json:"createChannel"`
  640. }
  641. query := fmt.Sprintf(`mutation { createChannel(input: {participants: ["%s"]}) { id } }`, strings.Join(participantIDs, `", "`))
  642. err := tc.client.Post(query, &response)
  643. if err != nil {
  644. t.Fatalf("Failed to create channel: %v", err)
  645. }
  646. tracker.Channels = append(tracker.Channels, response.CreateChannel.ID)
  647. }
  648. // Create Messages
  649. for _, msg := range seed.Messages {
  650. var response struct {
  651. CreateMessage struct {
  652. ID string `json:"id"`
  653. } `json:"createMessage"`
  654. }
  655. query := fmt.Sprintf(`mutation { createMessage(input: {conversationId: "%s", senderId: "%s", content: "%s"}) { id } }`, tracker.Channels[msg.ChannelIndex], tracker.Users[msg.SenderEmail], msg.Content)
  656. err := tc.client.Post(query, &response)
  657. if err != nil {
  658. t.Fatalf("Failed to create message: %v", err)
  659. }
  660. tracker.Messages = append(tracker.Messages, response.CreateMessage.ID)
  661. }
  662. }