setup_test.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. package setup
  2. import (
  3. "os"
  4. "path/filepath"
  5. "strings"
  6. "testing"
  7. "time"
  8. )
  9. func TestDecideAdminBootstrap(t *testing.T) {
  10. t.Parallel()
  11. tests := []struct {
  12. name string
  13. totalUsers int64
  14. adminUsers int64
  15. should bool
  16. reason string
  17. }{
  18. {
  19. name: "empty database should create admin",
  20. totalUsers: 0,
  21. adminUsers: 0,
  22. should: true,
  23. reason: adminBootstrapReasonEmptyDatabase,
  24. },
  25. {
  26. name: "admin exists should skip",
  27. totalUsers: 10,
  28. adminUsers: 1,
  29. should: false,
  30. reason: adminBootstrapReasonAdminExists,
  31. },
  32. {
  33. name: "users exist without admin should skip",
  34. totalUsers: 5,
  35. adminUsers: 0,
  36. should: false,
  37. reason: adminBootstrapReasonUsersExistWithoutAdmin,
  38. },
  39. }
  40. for _, tc := range tests {
  41. tc := tc
  42. t.Run(tc.name, func(t *testing.T) {
  43. t.Parallel()
  44. got := decideAdminBootstrap(tc.totalUsers, tc.adminUsers)
  45. if got.shouldCreate != tc.should {
  46. t.Fatalf("shouldCreate=%v, want %v", got.shouldCreate, tc.should)
  47. }
  48. if got.reason != tc.reason {
  49. t.Fatalf("reason=%q, want %q", got.reason, tc.reason)
  50. }
  51. })
  52. }
  53. }
  54. func TestSetupDefaultAdminConcurrency(t *testing.T) {
  55. t.Run("simple mode admin uses higher concurrency", func(t *testing.T) {
  56. t.Setenv("RUN_MODE", "simple")
  57. if got := setupDefaultAdminConcurrency(); got != simpleModeAdminConcurrency {
  58. t.Fatalf("setupDefaultAdminConcurrency()=%d, want %d", got, simpleModeAdminConcurrency)
  59. }
  60. })
  61. t.Run("standard mode keeps existing default", func(t *testing.T) {
  62. t.Setenv("RUN_MODE", "standard")
  63. if got := setupDefaultAdminConcurrency(); got != defaultUserConcurrency {
  64. t.Fatalf("setupDefaultAdminConcurrency()=%d, want %d", got, defaultUserConcurrency)
  65. }
  66. })
  67. }
  68. func TestNeedsSetupSkipsWhenSkipSetupIsEnabled(t *testing.T) {
  69. tests := []struct {
  70. name string
  71. value string
  72. }{
  73. {name: "true", value: "true"},
  74. {name: "one", value: "1"},
  75. {name: "yes", value: "yes"},
  76. {name: "trimmed mixed case true", value: " TrUe "},
  77. {name: "trimmed mixed case yes", value: " YeS "},
  78. }
  79. for _, tc := range tests {
  80. t.Run(tc.name, func(t *testing.T) {
  81. t.Setenv("DATA_DIR", t.TempDir())
  82. t.Setenv("SKIP_SETUP", tc.value)
  83. if NeedsSetup() {
  84. t.Fatalf("NeedsSetup() = true, want false when SKIP_SETUP is enabled")
  85. }
  86. })
  87. }
  88. }
  89. func TestNeedsSetupFallsBackToFileDetectionWhenSkipSetupIsDisabled(t *testing.T) {
  90. tests := []struct {
  91. name string
  92. skipSetupSet bool
  93. skipSetup string
  94. markerFile string
  95. want bool
  96. }{
  97. {
  98. name: "unset without installation files",
  99. want: true,
  100. },
  101. {
  102. name: "false without installation files",
  103. skipSetupSet: true,
  104. skipSetup: " false ",
  105. want: true,
  106. },
  107. {
  108. name: "invalid value without installation files",
  109. skipSetupSet: true,
  110. skipSetup: "enabled",
  111. want: true,
  112. },
  113. {
  114. name: "config file exists",
  115. skipSetupSet: true,
  116. skipSetup: "false",
  117. markerFile: ConfigFileName,
  118. want: false,
  119. },
  120. {
  121. name: "install lock file exists",
  122. skipSetupSet: true,
  123. skipSetup: "invalid",
  124. markerFile: InstallLockFile,
  125. want: false,
  126. },
  127. }
  128. for _, tc := range tests {
  129. t.Run(tc.name, func(t *testing.T) {
  130. dataDir := t.TempDir()
  131. t.Setenv("DATA_DIR", dataDir)
  132. if tc.skipSetupSet {
  133. t.Setenv("SKIP_SETUP", tc.skipSetup)
  134. } else {
  135. originalValue, wasSet := os.LookupEnv("SKIP_SETUP")
  136. if err := os.Unsetenv("SKIP_SETUP"); err != nil {
  137. t.Fatalf("Unsetenv(SKIP_SETUP) error = %v", err)
  138. }
  139. t.Cleanup(func() {
  140. if wasSet {
  141. _ = os.Setenv("SKIP_SETUP", originalValue)
  142. return
  143. }
  144. _ = os.Unsetenv("SKIP_SETUP")
  145. })
  146. }
  147. if tc.markerFile != "" {
  148. if err := os.WriteFile(filepath.Join(dataDir, tc.markerFile), nil, 0o600); err != nil {
  149. t.Fatalf("WriteFile(%s) error = %v", tc.markerFile, err)
  150. }
  151. }
  152. if got := NeedsSetup(); got != tc.want {
  153. t.Fatalf("NeedsSetup() = %v, want %v", got, tc.want)
  154. }
  155. })
  156. }
  157. }
  158. func TestSetupMigrationTimeout(t *testing.T) {
  159. t.Run("uses default timeout when unset", func(t *testing.T) {
  160. cfg := &SetupConfig{}
  161. if got := cfg.migrationTimeout(); got != 60*time.Second {
  162. t.Fatalf("migrationTimeout()=%s, want 60s", got)
  163. }
  164. })
  165. t.Run("uses configured timeout", func(t *testing.T) {
  166. cfg := &SetupConfig{MigrationTimeoutSeconds: 300}
  167. if got := cfg.migrationTimeout(); got != 300*time.Second {
  168. t.Fatalf("migrationTimeout()=%s, want 300s", got)
  169. }
  170. })
  171. }
  172. func TestWriteConfigFileKeepsDefaultUserConcurrency(t *testing.T) {
  173. t.Setenv("RUN_MODE", "simple")
  174. t.Setenv("DATA_DIR", t.TempDir())
  175. if err := writeConfigFile(&SetupConfig{}); err != nil {
  176. t.Fatalf("writeConfigFile() error = %v", err)
  177. }
  178. data, err := os.ReadFile(GetConfigFilePath())
  179. if err != nil {
  180. t.Fatalf("ReadFile() error = %v", err)
  181. }
  182. if !strings.Contains(string(data), "user_concurrency: 5") {
  183. t.Fatalf("config missing default user concurrency, got:\n%s", string(data))
  184. }
  185. }
  186. func TestWriteConfigFileIncludesRedisUsername(t *testing.T) {
  187. t.Setenv("DATA_DIR", t.TempDir())
  188. if err := writeConfigFile(&SetupConfig{
  189. Redis: RedisConfig{
  190. Host: "redis",
  191. Port: 6379,
  192. Username: "app-user",
  193. },
  194. }); err != nil {
  195. t.Fatalf("writeConfigFile() error = %v", err)
  196. }
  197. data, err := os.ReadFile(GetConfigFilePath())
  198. if err != nil {
  199. t.Fatalf("ReadFile() error = %v", err)
  200. }
  201. if !strings.Contains(string(data), "username: app-user") {
  202. t.Fatalf("config missing Redis username, got:\n%s", string(data))
  203. }
  204. }
  205. func TestBuildDatabaseConnectionDSNsUsesPostgresForBootstrap(t *testing.T) {
  206. cfg := &DatabaseConfig{
  207. Host: "db",
  208. Port: 5432,
  209. User: "sub2api",
  210. Password: "secret",
  211. DBName: "sub2api",
  212. SSLMode: "disable",
  213. }
  214. bootstrapDSN, targetDSN := buildDatabaseConnectionDSNs(cfg)
  215. if !strings.Contains(bootstrapDSN, "dbname=postgres") {
  216. t.Fatalf("bootstrap DSN = %q, want default postgres database", bootstrapDSN)
  217. }
  218. if strings.Contains(bootstrapDSN, "dbname=sub2api") {
  219. t.Fatalf("bootstrap DSN = %q, should not connect to target database before checking/creating it", bootstrapDSN)
  220. }
  221. if !strings.Contains(targetDSN, "dbname=sub2api") {
  222. t.Fatalf("target DSN = %q, want configured database", targetDSN)
  223. }
  224. }