github.com/diggerhq/digger/libs@v0.0.0-20240604170430-9d61cdf01cc5/locking/azure/storage_account_test.go (about) 1 package azure 2 3 import ( 4 "fmt" 5 "net" 6 "os" 7 "testing" 8 9 "github.com/google/uuid" 10 "github.com/stretchr/testify/suite" 11 ) 12 13 // Default values to connect to Azurite 14 const ( 15 AZURITE_SA_NAME = "devstoreaccount1" 16 AZURITE_CONN_STRING = "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;" 17 AZURITE_SHARED_KEY = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" 18 ) 19 20 var ( 21 usingRealSA = false // Whether we are running tests against a real storage account or not. 22 envNames = []string{ 23 "DIGGER_AZURE_CONNECTION_STRING", 24 "DIGGER_AZURE_SHARED_KEY", 25 "DIGGER_AZURE_SA_NAME", 26 "DIGGER_AZURE_TENANT_ID", 27 "DIGGER_AZURE_CLIENT_ID", 28 "DIGGER_AZURE_CLIENT_SECRET", 29 } 30 31 // Holds our current environment 32 envs = map[string]string{} 33 ) 34 35 type tt struct { 36 loadEnv func(*SALockTestSuite) 37 name string 38 } 39 40 // We use a test table so we can run our tests, 41 // using multiple authentication variations. 42 var ( 43 testCases = []tt{ 44 { 45 name: "Connection string authentication mode", 46 loadEnv: func(*SALockTestSuite) { 47 loadConnStringEnv() 48 }, 49 }, 50 { 51 name: "Shared Key authentication mode", 52 loadEnv: func(*SALockTestSuite) { 53 loadSharedKeyEnv() 54 }, 55 }, 56 { 57 name: "Client secret authentication mode", 58 loadEnv: func(s *SALockTestSuite) { 59 // Skip this test case if we are not testing on a real storage account 60 if !usingRealSA { 61 s.T().Skip("Client secret method can only be tested when used against a real storage account.") 62 return 63 } 64 loadClientSecretEnv() 65 }, 66 }, 67 } 68 ) 69 70 type SALockTestSuite struct { 71 suite.Suite 72 } 73 74 // Runs once before the test suite starts 75 func (suite *SALockTestSuite) SetupSuite() { 76 // Prepare environment variables 77 prepareEnv(suite) 78 79 // Make sure Azurite is started before the tests 80 // if we are not using a real storage account 81 if usingRealSA { 82 return 83 } 84 conn, err := net.Dial("tcp", "127.0.0.1:10002") 85 if err != nil { 86 suite.T().Fatalf("Please make sure 'Azurite' table service is started before running Azure tests, or use a real storage account.") 87 } 88 conn.Close() 89 90 cleanEnv() 91 } 92 93 // Runs after every test 94 func (suite *SALockTestSuite) TearDownTest() { 95 cleanEnv() 96 } 97 98 // Runs after every sub-test 99 func (suite *SALockTestSuite) TearDownSubTest() { 100 cleanEnv() 101 } 102 103 func (suite *SALockTestSuite) TestNewStorageAccountLock() { 104 for _, tc := range testCases { 105 suite.Run(tc.name, func() { 106 tc.loadEnv(suite) 107 sal, err := NewStorageAccountLock() 108 109 suite.NotNil(sal) 110 suite.NoError(err) 111 }) 112 } 113 } 114 115 func (suite *SALockTestSuite) TestNewStorageAccountLock_NoAuthMethods() { 116 sal, err := NewStorageAccountLock() 117 118 suite.Nil(sal) 119 suite.Error(err, "expected an error, since no authentication mecanism was provided") 120 } 121 122 func (suite *SALockTestSuite) TestNewStorageAccountLock_WithSharedKey_MissingAccountName() { 123 loadSharedKeyEnv() 124 os.Setenv("DIGGER_AZURE_SA_NAME", "") 125 126 sal, err := NewStorageAccountLock() 127 suite.Nil(sal) 128 suite.Error(err, "should have got an error") 129 } 130 131 func (suite *SALockTestSuite) TestNewStorageAccountLock_WithClientSecret_MissingEnv() { 132 loadClientSecretEnv() 133 os.Setenv("DIGGER_AZURE_CLIENT_ID", "") 134 135 sal, err := NewStorageAccountLock() 136 suite.Nil(sal) 137 suite.Error(err, "should have got an error") 138 } 139 140 func (suite *SALockTestSuite) TestLock_WhenNotLockedYet() { 141 for _, tc := range testCases { 142 suite.Run(tc.name, func() { 143 tc.loadEnv(suite) 144 sal, _ := NewStorageAccountLock() 145 ok, err := sal.Lock(18, generateResourceName()) 146 147 suite.True(ok, "lock acquisition should be true") 148 suite.NoError(err, "error while acquiring lock") 149 }) 150 } 151 } 152 153 func (suite *SALockTestSuite) TestLock_WhenAlreadyLocked() { 154 for _, tc := range testCases { 155 suite.Run(tc.name, func() { 156 tc.loadEnv(suite) 157 sal, err := NewStorageAccountLock() 158 suite.NotNil(sal) 159 suite.NoError(err) 160 resourceName := generateResourceName() 161 162 // Locking the first time 163 ok, err := sal.Lock(18, resourceName) 164 suite.True(ok, "lock acquisition should be true") 165 suite.NoError(err, "should not have got an error") 166 167 // Lock the second time on the same resource name 168 ok, err = sal.Lock(18, resourceName) 169 suite.False(ok, "lock acquisition should be false") 170 suite.NoError(err, "should not have got an error") 171 }) 172 } 173 } 174 175 func (suite *SALockTestSuite) TestUnlock() { 176 for _, tc := range testCases { 177 suite.Run(tc.name, func() { 178 tc.loadEnv(suite) 179 sal, _ := NewStorageAccountLock() 180 resourceName := generateResourceName() 181 182 // Locking 183 ok, err := sal.Lock(18, resourceName) 184 suite.True(ok, "lock acquisition should be true") 185 suite.NoError(err, "should not have got an error") 186 187 // Unlocking 188 ok, err = sal.Unlock(resourceName) 189 suite.True(ok) 190 suite.NoError(err, "should not have got an error") 191 }) 192 } 193 } 194 195 func (suite *SALockTestSuite) TestUnlock_WhenLockDoesNotExist() { 196 for _, tc := range testCases { 197 suite.Run(tc.name, func() { 198 tc.loadEnv(suite) 199 sal, err := NewStorageAccountLock() 200 suite.NotNil(sal) 201 suite.Require().NoError(err) 202 203 // Unlocking 204 resourceName := generateResourceName() 205 ok, err := sal.Unlock(resourceName) 206 suite.False(ok) 207 suite.Error(err, "should have got an error") 208 }) 209 } 210 } 211 212 func (suite *SALockTestSuite) TestUnlock_Twice_WhenLockExist() { 213 for _, tc := range testCases { 214 suite.Run(tc.name, func() { 215 tc.loadEnv(suite) 216 sal, _ := NewStorageAccountLock() 217 resourceName := generateResourceName() 218 219 // Locking 220 ok, err := sal.Lock(18, resourceName) 221 suite.True(ok, "lock acquisition should be true") 222 suite.NoError(err, "should not have got an error") 223 224 // Unlocking the first time 225 ok, err = sal.Unlock(resourceName) 226 suite.True(ok) 227 suite.NoError(err, "should not have got an error") 228 229 // Unlocking the second time 230 ok, err = sal.Unlock(resourceName) 231 suite.False(ok) 232 suite.Error(err, "should have got an error") 233 }) 234 } 235 } 236 237 func (suite *SALockTestSuite) TestGetLock() { 238 for _, tc := range testCases { 239 suite.Run(tc.name, func() { 240 tc.loadEnv(suite) 241 sal, err := NewStorageAccountLock() 242 suite.NotNil(sal) 243 suite.Require().NoError(err) 244 245 // Locking 246 resourceName := generateResourceName() 247 ok, err := sal.Lock(21, resourceName) 248 suite.Require().True(ok, "lock acquisition should be true") 249 suite.Require().NoError(err, "should not have got an error") 250 251 // Get the lock 252 transactionId, err := sal.GetLock(resourceName) 253 suite.Equal(21, *transactionId, "transaction id mismatch") 254 suite.NoError(err, "should not have got an error") 255 }) 256 } 257 } 258 259 func (suite *SALockTestSuite) TestGetLock_WhenLockDoesNotExist() { 260 for _, tc := range testCases { 261 suite.Run(tc.name, func() { 262 tc.loadEnv(suite) 263 sal, err := NewStorageAccountLock() 264 suite.Require().NotNil(sal) 265 suite.Require().NoError(err) 266 267 // Get a lock that doesn't exist 268 resourceName := generateResourceName() 269 transactionId, err := sal.GetLock(resourceName) 270 suite.Nil(transactionId, "transaction id should be nil") 271 suite.NoError(err, "should not have got an error") 272 }) 273 } 274 } 275 276 func (suite *SALockTestSuite) TestGetLock_WithForbiddenCharacters() { 277 for _, tc := range testCases { 278 suite.Run(tc.name, func() { 279 tc.loadEnv(suite) 280 sal, err := NewStorageAccountLock() 281 suite.NotNil(sal) 282 suite.Require().NoError(err) 283 284 // Locking 285 resourceName := fmt.Sprintf("digger/diggerhq/%s#project/", generateResourceName()) 286 ok, err := sal.Lock(21, resourceName) 287 suite.Require().True(ok, "lock acquisition should be true") 288 suite.Require().NoError(err, "should not have got an error") 289 290 // Get the lock 291 transactionId, err := sal.GetLock(resourceName) 292 suite.Equal(21, *transactionId, "transaction id mismatch") 293 suite.NoError(err, "should not have got an error") 294 }) 295 } 296 } 297 298 // Entrypoint of the test suite 299 func TestStorageAccountLockTestSuite(t *testing.T) { 300 // To test Azure Storage account we can either: 301 // 1. use a real storage account if we have one 302 // 2. or use a local storage account emulator (Azurite) 303 304 useRealSA := os.Getenv("DIGGER_TEST_USE_REAL_SA") 305 306 // Override the service url format 307 // if we are not using a real storage account 308 // We'll be relying on Azurite emulator which has 309 // a different url format 310 if useRealSA == "" || useRealSA == "0" { 311 SERVICE_URL_FORMAT = "http://127.0.0.1:10002/%s" 312 usingRealSA = false 313 suite.Run(t, new(SALockTestSuite)) 314 return 315 } 316 317 usingRealSA = true 318 suite.Run(t, new(SALockTestSuite)) 319 } 320 321 // Generate unique string to be used as resource name 322 // so we don't have to delete the entity 323 func generateResourceName() string { 324 return uuid.New().String() 325 } 326 327 // Initialize and save environment variables into a map. 328 // This is useful so we can alter environment variables 329 // without losing their initial values. 330 331 // This comes is handy when we want to test cases 332 // where an environment variable is not defined. 333 func prepareEnv(s *SALockTestSuite) { 334 // When using Azurite, environment variables are known 335 if !usingRealSA { 336 envs["DIGGER_AZURE_SHARED_KEY"] = AZURITE_SHARED_KEY 337 envs["DIGGER_AZURE_SA_NAME"] = AZURITE_SA_NAME 338 envs["DIGGER_AZURE_CONNECTION_STRING"] = AZURITE_CONN_STRING 339 return 340 } 341 342 // When using real storage account, environment 343 // variables are not known, and must be injected by the user 344 // before starting our tests. 345 for _, env := range envNames { 346 envValue, exists := os.LookupEnv(env) 347 if !exists { 348 s.T().Fatalf("Since 'DIGGER_TEST_USE_REAL_SA' has been set, '%s' environment variable must also be set before starting the tests.", env) 349 } 350 351 envs[env] = envValue 352 } 353 } 354 355 func loadSharedKeyEnv() { 356 os.Setenv("DIGGER_AZURE_AUTH_METHOD", "SHARED_KEY") 357 358 os.Setenv("DIGGER_AZURE_SHARED_KEY", envs["DIGGER_AZURE_SHARED_KEY"]) 359 os.Setenv("DIGGER_AZURE_SA_NAME", envs["DIGGER_AZURE_SA_NAME"]) 360 } 361 362 func loadConnStringEnv() { 363 os.Setenv("DIGGER_AZURE_AUTH_METHOD", "CONNECTION_STRING") 364 os.Setenv("DIGGER_AZURE_CONNECTION_STRING", envs["DIGGER_AZURE_CONNECTION_STRING"]) 365 } 366 367 func loadClientSecretEnv() { 368 os.Setenv("DIGGER_AZURE_AUTH_METHOD", "CLIENT_SECRET") 369 370 os.Setenv("DIGGER_AZURE_TENANT_ID", envs["DIGGER_AZURE_TENANT_ID"]) 371 os.Setenv("DIGGER_AZURE_CLIENT_ID", envs["DIGGER_AZURE_CLIENT_ID"]) 372 os.Setenv("DIGGER_AZURE_CLIENT_SECRET", envs["DIGGER_AZURE_CLIENT_SECRET"]) 373 os.Setenv("DIGGER_AZURE_SA_NAME", envs["DIGGER_AZURE_SA_NAME"]) 374 } 375 376 // Clean environment variables 377 func cleanEnv() { 378 for _, env := range envNames { 379 os.Setenv(env, "") 380 } 381 }