@@ -22,7 +22,12 @@ enum TestPhase {
22
22
run2,
23
23
}
24
24
25
- @GenerateMocks (< Type > [AuthClient ])
25
+ @GenerateMocks (< Type > [
26
+ AuthClient ,
27
+ StorageApi
28
+ ], customMocks: < MockSpec <dynamic >> [
29
+ MockSpec <ObjectsResource >(onMissingStub: OnMissingStub .returnDefault)
30
+ ])
26
31
void main () {
27
32
const Duration kDelayStep = Duration (milliseconds: 10 );
28
33
final Map <String , dynamic >? credentialsJson = getTestGcpCredentialsJson ();
@@ -36,7 +41,7 @@ void main() {
36
41
Zone .current.fork (specification: spec).run <void >(() {
37
42
fakeAsync ((FakeAsync fakeAsync) {
38
43
final MockAuthClient mockClient = MockAuthClient ();
39
- final GcsLock lock = GcsLock (mockClient, 'mockBucket' );
44
+ final GcsLock lock = GcsLock (StorageApi ( mockClient) , 'mockBucket' );
40
45
when (mockClient.send (any)).thenThrow (DetailedApiRequestError (412 , '' ));
41
46
final Future <void > runFinished =
42
47
lock.protectedRun ('mock.lock' , () async {});
@@ -63,7 +68,7 @@ void main() {
63
68
test ('GcsLock integration test: single protectedRun is successful' , () async {
64
69
final AutoRefreshingAuthClient client = await clientViaServiceAccount (
65
70
ServiceAccountCredentials .fromJson (credentialsJson), Storage .SCOPES );
66
- final GcsLock lock = GcsLock (client, kTestBucketName);
71
+ final GcsLock lock = GcsLock (StorageApi ( client) , kTestBucketName);
67
72
int testValue = 0 ;
68
73
await lock.protectedRun ('test.lock' , () async {
69
74
testValue = 1 ;
@@ -74,8 +79,8 @@ void main() {
74
79
test ('GcsLock integration test: protectedRun is exclusive' , () async {
75
80
final AutoRefreshingAuthClient client = await clientViaServiceAccount (
76
81
ServiceAccountCredentials .fromJson (credentialsJson), Storage .SCOPES );
77
- final GcsLock lock1 = GcsLock (client, kTestBucketName);
78
- final GcsLock lock2 = GcsLock (client, kTestBucketName);
82
+ final GcsLock lock1 = GcsLock (StorageApi ( client) , kTestBucketName);
83
+ final GcsLock lock2 = GcsLock (StorageApi ( client) , kTestBucketName);
79
84
80
85
TestPhase phase = TestPhase .run1;
81
86
final Completer <void > started1 = Completer <void >();
@@ -105,4 +110,39 @@ void main() {
105
110
await finished1;
106
111
await finished2;
107
112
}, skip: credentialsJson == null );
113
+
114
+ test ('GcsLock attempts to unlock again on a DetailedApiRequestError' ,
115
+ () async {
116
+ fakeAsync ((FakeAsync fakeAsync) {
117
+ final StorageApi mockStorageApi = MockStorageApi ();
118
+ final ObjectsResource mockObjectsResource = MockObjectsResource ();
119
+ final GcsLock gcsLock = GcsLock (mockStorageApi, kTestBucketName);
120
+ const String lockFileName = 'test.lock' ;
121
+ when (mockStorageApi.objects).thenReturn (mockObjectsResource);
122
+
123
+ // Simulate a failure to delete a lock file.
124
+ when (mockObjectsResource.delete (kTestBucketName, lockFileName))
125
+ .thenThrow (DetailedApiRequestError (504 , '' ));
126
+
127
+ gcsLock.protectedRun (lockFileName, () async {});
128
+
129
+ // Allow time to pass by to ensure deleting the lock file is retried multiple times.
130
+ fakeAsync.elapse (const Duration (milliseconds: 30 ));
131
+ verify (mockObjectsResource.delete (kTestBucketName, lockFileName))
132
+ .called (3 );
133
+
134
+ // Simulate a successful deletion of the lock file.
135
+ when (mockObjectsResource.delete (kTestBucketName, lockFileName))
136
+ .thenAnswer ((_) => Future <void >(
137
+ () {
138
+ return ;
139
+ },
140
+ ));
141
+
142
+ // At this point, there should only be one more (successful) attempt to delete the lock file.
143
+ fakeAsync.elapse (const Duration (minutes: 2 ));
144
+ verify (mockObjectsResource.delete (kTestBucketName, lockFileName))
145
+ .called (1 );
146
+ });
147
+ });
108
148
}
0 commit comments