auth_cache_invalidation_outbox_integration_test.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. //go:build integration
  2. package repository
  3. import (
  4. "context"
  5. "crypto/sha256"
  6. "encoding/hex"
  7. "fmt"
  8. "testing"
  9. "time"
  10. "github.com/Wei-Shaw/sub2api/internal/service"
  11. "github.com/stretchr/testify/require"
  12. )
  13. func TestAuthCacheInvalidationTriggers_CoverSecurityMutationsOnly(t *testing.T) {
  14. ctx := context.Background()
  15. suffix := time.Now().UnixNano()
  16. group := mustCreateGroup(t, integrationEntClient, &service.Group{
  17. Name: fmt.Sprintf("auth-outbox-group-%d", suffix), RateMultiplier: 1, IsExclusive: true,
  18. })
  19. user := mustCreateUser(t, integrationEntClient, &service.User{
  20. Email: fmt.Sprintf("auth-outbox-%d@example.com", suffix), Concurrency: 5,
  21. })
  22. groupID := group.ID
  23. keyValue := fmt.Sprintf("sk-auth-outbox-%d", suffix)
  24. apiKeyRepo := NewAPIKeyRepository(integrationEntClient, integrationDB)
  25. key := &service.APIKey{UserID: user.ID, GroupID: &groupID, Key: keyValue, Name: "outbox", Status: service.StatusActive}
  26. require.NoError(t, apiKeyRepo.Create(ctx, key))
  27. sum := sha256.Sum256([]byte(keyValue))
  28. cacheKey := hex.EncodeToString(sum[:])
  29. clear := func() {
  30. _, err := integrationDB.ExecContext(ctx, "DELETE FROM auth_cache_invalidation_outbox WHERE cache_key = $1", cacheKey)
  31. require.NoError(t, err)
  32. }
  33. count := func() int {
  34. var value int
  35. require.NoError(t, integrationDB.QueryRowContext(ctx,
  36. "SELECT COUNT(*) FROM auth_cache_invalidation_outbox WHERE cache_key = $1", cacheKey).Scan(&value))
  37. return value
  38. }
  39. clear()
  40. t.Cleanup(clear)
  41. t.Cleanup(func() {
  42. // Keep the shared integration database isolated for suites that assert
  43. // platform-wide group counts. The final clear cleanup runs after this one
  44. // and removes invalidations emitted by these hard deletes.
  45. _, err := integrationDB.ExecContext(ctx, "DELETE FROM user_allowed_groups WHERE user_id = $1 OR group_id = $2", user.ID, group.ID)
  46. require.NoError(t, err)
  47. _, err = integrationDB.ExecContext(ctx, "DELETE FROM api_keys WHERE id = $1", key.ID)
  48. require.NoError(t, err)
  49. _, err = integrationDB.ExecContext(ctx, "DELETE FROM users WHERE id = $1", user.ID)
  50. require.NoError(t, err)
  51. _, err = integrationDB.ExecContext(ctx, "DELETE FROM groups WHERE id = $1", group.ID)
  52. require.NoError(t, err)
  53. })
  54. _, err := integrationDB.ExecContext(ctx, `
  55. UPDATE api_keys
  56. SET quota_used = quota_used + 1,
  57. usage_5h = usage_5h + 1,
  58. last_used_at = NOW()
  59. WHERE id = $1`, key.ID)
  60. require.NoError(t, err)
  61. require.Zero(t, count(), "usage-only key updates must not enqueue")
  62. _, err = integrationDB.ExecContext(ctx, "UPDATE api_keys SET status = 'disabled' WHERE id = $1", key.ID)
  63. require.NoError(t, err)
  64. require.Equal(t, 1, count(), "key disable must enqueue")
  65. clear()
  66. _, err = integrationDB.ExecContext(ctx, "UPDATE api_keys SET status = 'active' WHERE id = $1", key.ID)
  67. require.NoError(t, err)
  68. clear()
  69. userRepo := NewUserRepository(integrationEntClient, integrationDB)
  70. loadedUser, err := userRepo.GetByID(ctx, user.ID)
  71. require.NoError(t, err)
  72. _, err = userRepo.AdjustBalance(ctx, loadedUser.ID, 10)
  73. require.NoError(t, err)
  74. require.Zero(t, count(), "balance update with unchanged allowed groups must not enqueue")
  75. _, err = integrationDB.ExecContext(ctx, "UPDATE users SET status = 'disabled' WHERE id = $1", user.ID)
  76. require.NoError(t, err)
  77. require.Equal(t, 1, count(), "user disable must enqueue all active keys")
  78. clear()
  79. _, err = integrationDB.ExecContext(ctx, "UPDATE users SET status = 'active' WHERE id = $1", user.ID)
  80. require.NoError(t, err)
  81. clear()
  82. _, err = integrationDB.ExecContext(ctx, "UPDATE groups SET name = name || '-cosmetic' WHERE id = $1", group.ID)
  83. require.NoError(t, err)
  84. require.Zero(t, count(), "cosmetic group update must not enqueue")
  85. _, err = integrationDB.ExecContext(ctx, "UPDATE groups SET allow_image_generation = NOT allow_image_generation WHERE id = $1", group.ID)
  86. require.NoError(t, err)
  87. require.Equal(t, 1, count(), "image-generation permission changes must enqueue bound keys")
  88. clear()
  89. _, err = integrationDB.ExecContext(ctx, "UPDATE groups SET status = 'disabled' WHERE id = $1", group.ID)
  90. require.NoError(t, err)
  91. require.Equal(t, 1, count(), "group disable must enqueue bound keys")
  92. clear()
  93. _, err = integrationDB.ExecContext(ctx, "UPDATE groups SET status = 'active' WHERE id = $1", group.ID)
  94. require.NoError(t, err)
  95. clear()
  96. _, err = integrationDB.ExecContext(ctx,
  97. "INSERT INTO user_allowed_groups (user_id, group_id) VALUES ($1, $2)", user.ID, group.ID)
  98. require.NoError(t, err)
  99. clear()
  100. _, err = integrationDB.ExecContext(ctx,
  101. "DELETE FROM user_allowed_groups WHERE user_id = $1 AND group_id = $2", user.ID, group.ID)
  102. require.NoError(t, err)
  103. require.Equal(t, 1, count(), "exclusive-group revocation must enqueue")
  104. clear()
  105. require.NoError(t, apiKeyRepo.DeleteWithAudit(ctx, key.ID))
  106. require.Equal(t, 1, count(), "tombstone delete must hash OLD.key exactly once")
  107. var stored string
  108. require.NoError(t, integrationDB.QueryRowContext(ctx,
  109. "SELECT cache_key FROM auth_cache_invalidation_outbox WHERE cache_key = $1 LIMIT 1", cacheKey).Scan(&stored))
  110. require.Equal(t, cacheKey, stored)
  111. require.NotContains(t, stored, keyValue)
  112. }