allowed_groups_contract_integration_test.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. //go:build integration
  2. package repository
  3. import (
  4. "context"
  5. "fmt"
  6. "strings"
  7. "testing"
  8. "github.com/Wei-Shaw/sub2api/internal/service"
  9. "github.com/stretchr/testify/require"
  10. )
  11. func uniqueTestValue(t *testing.T, prefix string) string {
  12. t.Helper()
  13. safeName := strings.NewReplacer("/", "_", " ", "_").Replace(t.Name())
  14. return fmt.Sprintf("%s-%s", prefix, safeName)
  15. }
  16. func TestUserRepository_RemoveGroupFromAllowedGroups_RemovesAllOccurrences(t *testing.T) {
  17. ctx := context.Background()
  18. tx := testEntTx(t)
  19. entClient := tx.Client()
  20. targetGroup, err := entClient.Group.Create().
  21. SetName(uniqueTestValue(t, "target-group")).
  22. SetStatus(service.StatusActive).
  23. Save(ctx)
  24. require.NoError(t, err)
  25. otherGroup, err := entClient.Group.Create().
  26. SetName(uniqueTestValue(t, "other-group")).
  27. SetStatus(service.StatusActive).
  28. Save(ctx)
  29. require.NoError(t, err)
  30. repo := newUserRepositoryWithSQL(entClient, tx)
  31. u1 := &service.User{
  32. Email: uniqueTestValue(t, "u1") + "@example.com",
  33. PasswordHash: "test-password-hash",
  34. Role: service.RoleUser,
  35. Status: service.StatusActive,
  36. Concurrency: 5,
  37. AllowedGroups: []int64{targetGroup.ID, otherGroup.ID},
  38. }
  39. require.NoError(t, repo.Create(ctx, u1))
  40. u2 := &service.User{
  41. Email: uniqueTestValue(t, "u2") + "@example.com",
  42. PasswordHash: "test-password-hash",
  43. Role: service.RoleUser,
  44. Status: service.StatusActive,
  45. Concurrency: 5,
  46. AllowedGroups: []int64{targetGroup.ID},
  47. }
  48. require.NoError(t, repo.Create(ctx, u2))
  49. u3 := &service.User{
  50. Email: uniqueTestValue(t, "u3") + "@example.com",
  51. PasswordHash: "test-password-hash",
  52. Role: service.RoleUser,
  53. Status: service.StatusActive,
  54. Concurrency: 5,
  55. AllowedGroups: []int64{otherGroup.ID},
  56. }
  57. require.NoError(t, repo.Create(ctx, u3))
  58. affected, err := repo.RemoveGroupFromAllowedGroups(ctx, targetGroup.ID)
  59. require.NoError(t, err)
  60. require.Equal(t, int64(2), affected)
  61. u1After, err := repo.GetByID(ctx, u1.ID)
  62. require.NoError(t, err)
  63. require.NotContains(t, u1After.AllowedGroups, targetGroup.ID)
  64. require.Contains(t, u1After.AllowedGroups, otherGroup.ID)
  65. u2After, err := repo.GetByID(ctx, u2.ID)
  66. require.NoError(t, err)
  67. require.NotContains(t, u2After.AllowedGroups, targetGroup.ID)
  68. }
  69. func TestGroupRepository_DeleteCascade_PreservesApiKeyGroupID(t *testing.T) {
  70. ctx := context.Background()
  71. tx := testEntTx(t)
  72. entClient := tx.Client()
  73. targetGroup, err := entClient.Group.Create().
  74. SetName(uniqueTestValue(t, "delete-cascade-target")).
  75. SetStatus(service.StatusActive).
  76. Save(ctx)
  77. require.NoError(t, err)
  78. otherGroup, err := entClient.Group.Create().
  79. SetName(uniqueTestValue(t, "delete-cascade-other")).
  80. SetStatus(service.StatusActive).
  81. Save(ctx)
  82. require.NoError(t, err)
  83. userRepo := newUserRepositoryWithSQL(entClient, tx)
  84. groupRepo := newGroupRepositoryWithSQL(entClient, tx)
  85. apiKeyRepo := newAPIKeyRepositoryWithSQL(entClient, tx)
  86. u := &service.User{
  87. Email: uniqueTestValue(t, "cascade-user") + "@example.com",
  88. PasswordHash: "test-password-hash",
  89. Role: service.RoleUser,
  90. Status: service.StatusActive,
  91. Concurrency: 5,
  92. AllowedGroups: []int64{targetGroup.ID, otherGroup.ID},
  93. }
  94. require.NoError(t, userRepo.Create(ctx, u))
  95. key := &service.APIKey{
  96. UserID: u.ID,
  97. Key: uniqueTestValue(t, "sk-test-delete-cascade"),
  98. Name: "test key",
  99. GroupID: &targetGroup.ID,
  100. Status: service.StatusActive,
  101. }
  102. require.NoError(t, apiKeyRepo.Create(ctx, key))
  103. _, err = groupRepo.DeleteCascade(ctx, targetGroup.ID)
  104. require.NoError(t, err)
  105. // Deleted group should be hidden by default queries (soft-delete semantics).
  106. _, err = groupRepo.GetByID(ctx, targetGroup.ID)
  107. require.ErrorIs(t, err, service.ErrGroupNotFound)
  108. activeGroups, err := groupRepo.ListActive(ctx)
  109. require.NoError(t, err)
  110. for _, g := range activeGroups {
  111. require.NotEqual(t, targetGroup.ID, g.ID)
  112. }
  113. // User.allowed_groups should no longer include the deleted group.
  114. uAfter, err := userRepo.GetByID(ctx, u.ID)
  115. require.NoError(t, err)
  116. require.NotContains(t, uAfter.AllowedGroups, targetGroup.ID)
  117. require.Contains(t, uAfter.AllowedGroups, otherGroup.ID)
  118. // API keys keep their group_id so auth can reject keys bound to a deleted group.
  119. keyAfter, err := apiKeyRepo.GetByID(ctx, key.ID)
  120. require.NoError(t, err)
  121. require.NotNil(t, keyAfter.GroupID)
  122. require.Equal(t, targetGroup.ID, *keyAfter.GroupID)
  123. require.Nil(t, keyAfter.Group)
  124. }