@react-native-firebase/storage 26.3.3 → 27.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/RNFBStorage.podspec +9 -4
  3. package/android/build.gradle +6 -0
  4. package/android/src/main/java/io/invertase/firebase/storage/NativeRNFBTurboStorage.java +24 -4
  5. package/android/src/main/java/io/invertase/firebase/storage/RNFBStorageTaskRegistry.kt +88 -0
  6. package/android/src/main/java/io/invertase/firebase/storage/ReactNativeFirebaseStorageTask.java +25 -46
  7. package/android/src/main/java/io/invertase/firebase/storage/StoragePendingHandle.java +30 -0
  8. package/android/src/test/java/io/invertase/firebase/storage/RNFBStorageTaskRegistryTest.java +237 -0
  9. package/android/src/test/java/io/invertase/firebase/storage/ReactNativeFirebaseStorageTaskTest.java +285 -0
  10. package/dist/module/index.js +1 -2
  11. package/dist/module/index.js.map +1 -1
  12. package/dist/module/version.js +1 -1
  13. package/dist/typescript/lib/index.d.ts +1 -1
  14. package/dist/typescript/lib/types/internal.d.ts +2 -3
  15. package/dist/typescript/lib/types/internal.d.ts.map +1 -1
  16. package/dist/typescript/lib/version.d.ts +1 -1
  17. package/ios/RNFBStorage/RNFBStorageCommon.m +17 -98
  18. package/ios/RNFBStorage/RNFBStorageErrorCodeMapper.swift +122 -0
  19. package/ios/RNFBStorage/RNFBStorageErrorSnapshotDictBuilder.swift +70 -0
  20. package/ios/RNFBStorage/RNFBStorageHelper.m +38 -13
  21. package/ios/RNFBStorage/RNFBStorageTaskRegistry.h +42 -0
  22. package/ios/RNFBStorage/RNFBStorageTaskRegistry.m +87 -0
  23. package/ios/RNFBStorage/RNFBStorageTaskRegistryStorage.swift +87 -0
  24. package/ios/RNFBStorage/RNFBStorageTaskStatusMapper.swift +57 -0
  25. package/ios/RNFBStorage.xcodeproj/project.pbxproj +26 -0
  26. package/ios/RNFBStorageUnitTests/RNFBStorageErrorCodeMapperFacadeTests.m +91 -0
  27. package/ios/RNFBStorageUnitTests/RNFBStorageErrorCodeMapperTests.swift +211 -0
  28. package/ios/RNFBStorageUnitTests/RNFBStorageErrorSnapshotDictBuilderFacadeTests.m +125 -0
  29. package/ios/RNFBStorageUnitTests/RNFBStorageTaskRegistryStorageTests.swift +154 -0
  30. package/ios/RNFBStorageUnitTests/RNFBStorageTaskRegistryTests.m +225 -0
  31. package/ios/RNFBStorageUnitTests/RNFBStorageTaskStatusMapperFacadeTests.m +52 -0
  32. package/ios/RNFBStorageUnitTests/RNFBStorageTaskStatusMapperTests.swift +58 -0
  33. package/ios/RNFBStorageUnitTests/RNFBStorageUnitTests.xcodeproj/project.pbxproj +301 -0
  34. package/ios/RNFBStorageUnitTests/RNFBStorageUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBStorageUnitTests.xcscheme +69 -0
  35. package/lib/index.ts +1 -1
  36. package/lib/types/internal.ts +5 -4
  37. package/lib/version.ts +1 -1
  38. package/package.json +5 -4
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ #import <XCTest/XCTest.h>
18
+
19
+ #import "RNFBHandleMapStorage-Swift.inc"
20
+
21
+ @interface RNFBStorageErrorCodeMapperFacadeTests : XCTestCase
22
+ @end
23
+
24
+ @implementation RNFBStorageErrorCodeMapperFacadeTests
25
+
26
+ - (NSError *)errorWithCode:(NSInteger)code description:(NSString *)description {
27
+ return [NSError errorWithDomain:@"FIRStorageErrorDomain"
28
+ code:code
29
+ userInfo:@{NSLocalizedDescriptionKey : description}];
30
+ }
31
+
32
+ - (void)testObjCSelectorNilAndKnownCodes {
33
+ NSArray *nilPair = [RNFBStorageErrorCodeMapper codeAndMessageForError:nil];
34
+ XCTAssertEqualObjects(nilPair[0], @"unknown");
35
+ XCTAssertEqualObjects(nilPair[1], @"An unknown error has occurred.");
36
+
37
+ // FIRStorageErrorCodeObjectNotFound
38
+ NSArray *notFound = [RNFBStorageErrorCodeMapper codeAndMessageForError:[self errorWithCode:-13010
39
+ description:@"x"]];
40
+ XCTAssertEqualObjects(notFound[0], @"object-not-found");
41
+ XCTAssertEqualObjects(notFound[1], @"No object exists at the desired reference.");
42
+
43
+ // FIRStorageErrorCodeCancelled
44
+ NSArray *cancelled = [RNFBStorageErrorCodeMapper
45
+ codeAndMessageForError:[self errorWithCode:-13040 description:@"x"]];
46
+ XCTAssertEqualObjects(cancelled[0], @"cancelled");
47
+ XCTAssertEqualObjects(cancelled[1], @"User cancelled the operation.");
48
+ }
49
+
50
+ - (void)testObjCSelectorUnknownPermissionAndResponseBody {
51
+ // Permission-denied underlying → invalid-device-file-path
52
+ NSError *underlying = [NSError
53
+ errorWithDomain:NSPOSIXErrorDomain
54
+ code:1
55
+ userInfo:@{
56
+ NSLocalizedDescriptionKey : @"The operation couldn’t be completed. Permission denied"
57
+ }];
58
+ NSError *permissionError = [NSError errorWithDomain:@"FIRStorageErrorDomain"
59
+ code:-13000
60
+ userInfo:@{NSUnderlyingErrorKey : underlying}];
61
+ NSArray *permissionPair = [RNFBStorageErrorCodeMapper codeAndMessageForError:permissionError];
62
+ XCTAssertEqualObjects(permissionPair[0], @"invalid-device-file-path");
63
+ XCTAssertEqualObjects(permissionPair[1],
64
+ @"The specified device file path is invalid or is restricted.");
65
+
66
+ // ResponseBody → formatted unknown message
67
+ NSError *bodyError = [NSError errorWithDomain:@"FIRStorageErrorDomain"
68
+ code:-13000
69
+ userInfo:@{@"ResponseBody" : @"backend detail"}];
70
+ NSArray *bodyPair = [RNFBStorageErrorCodeMapper codeAndMessageForError:bodyError];
71
+ XCTAssertEqualObjects(bodyPair[0], @"unknown");
72
+ XCTAssertEqualObjects(bodyPair[1],
73
+ @"An unknown error has occurred. (underlying reason 'backend detail')");
74
+ }
75
+
76
+ - (void)testObjCSelectorDefaultKeepsLocalizedDescription {
77
+ // FIRStorageErrorCodeInvalidArgument
78
+ NSArray *pair = [RNFBStorageErrorCodeMapper
79
+ codeAndMessageForError:[self errorWithCode:-13050 description:@"keep me"]];
80
+ XCTAssertEqualObjects(pair[0], @"unknown");
81
+ XCTAssertEqualObjects(pair[1], @"keep me");
82
+ }
83
+
84
+ - (void)testFacadePublicSelectorUnchanged {
85
+ XCTAssertTrue([RNFBStorageErrorCodeMapper respondsToSelector:@selector(codeAndMessageForError:)]);
86
+ NSMethodSignature *signature =
87
+ [RNFBStorageErrorCodeMapper methodSignatureForSelector:@selector(codeAndMessageForError:)];
88
+ XCTAssertEqual(strcmp(signature.methodReturnType, @encode(id)), 0);
89
+ }
90
+
91
+ @end
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import XCTest
18
+
19
+ final class RNFBStorageErrorCodeMapperTests: XCTestCase {
20
+ /// `FIRStorageErrorCodeUnknown`
21
+ private let codeUnknown = -13000
22
+ /// `FIRStorageErrorCodeObjectNotFound`
23
+ private let codeObjectNotFound = -13010
24
+ /// `FIRStorageErrorCodeBucketNotFound`
25
+ private let codeBucketNotFound = -13011
26
+ /// `FIRStorageErrorCodeProjectNotFound`
27
+ private let codeProjectNotFound = -13012
28
+ /// `FIRStorageErrorCodeQuotaExceeded`
29
+ private let codeQuotaExceeded = -13013
30
+ /// `FIRStorageErrorCodeUnauthenticated`
31
+ private let codeUnauthenticated = -13020
32
+ /// `FIRStorageErrorCodeUnauthorized`
33
+ private let codeUnauthorized = -13021
34
+ /// `FIRStorageErrorCodeRetryLimitExceeded`
35
+ private let codeRetryLimitExceeded = -13030
36
+ /// `FIRStorageErrorCodeNonMatchingChecksum`
37
+ private let codeNonMatchingChecksum = -13031
38
+ /// `FIRStorageErrorCodeDownloadSizeExceeded`
39
+ private let codeDownloadSizeExceeded = -13032
40
+ /// `FIRStorageErrorCodeCancelled`
41
+ private let codeCancelled = -13040
42
+
43
+ private func makeError(code: Int, description: String = "native", userInfo: [String: Any] = [:])
44
+ -> NSError
45
+ {
46
+ var info = userInfo
47
+ if info[NSLocalizedDescriptionKey] == nil {
48
+ info[NSLocalizedDescriptionKey] = description
49
+ }
50
+ return NSError(domain: "FIRStorageErrorDomain", code: code, userInfo: info)
51
+ }
52
+
53
+ func testNilErrorReturnsUnknownPair() {
54
+ let result = RNFBStorageErrorCodeMapper.codeAndMessage(for: nil)
55
+ XCTAssertEqual(result, ["unknown", "An unknown error has occurred."])
56
+ }
57
+
58
+ func testObjectNotFound() {
59
+ let result = RNFBStorageErrorCodeMapper.codeAndMessage(
60
+ for: makeError(code: codeObjectNotFound)
61
+ )
62
+ XCTAssertEqual(result, ["object-not-found", "No object exists at the desired reference."])
63
+ }
64
+
65
+ func testBucketNotFound() {
66
+ let result = RNFBStorageErrorCodeMapper.codeAndMessage(
67
+ for: makeError(code: codeBucketNotFound)
68
+ )
69
+ XCTAssertEqual(
70
+ result,
71
+ ["bucket-not-found", "No bucket is configured for Firebase Storage."]
72
+ )
73
+ }
74
+
75
+ func testProjectNotFound() {
76
+ let result = RNFBStorageErrorCodeMapper.codeAndMessage(
77
+ for: makeError(code: codeProjectNotFound)
78
+ )
79
+ XCTAssertEqual(
80
+ result,
81
+ ["project-not-found", "No project is configured for Firebase Storage."]
82
+ )
83
+ }
84
+
85
+ func testQuotaExceeded() {
86
+ let result = RNFBStorageErrorCodeMapper.codeAndMessage(
87
+ for: makeError(code: codeQuotaExceeded)
88
+ )
89
+ XCTAssertEqual(
90
+ result,
91
+ ["quota-exceeded", "Quota on your Firebase Storage bucket has been exceeded."]
92
+ )
93
+ }
94
+
95
+ func testUnauthenticated() {
96
+ let result = RNFBStorageErrorCodeMapper.codeAndMessage(
97
+ for: makeError(code: codeUnauthenticated)
98
+ )
99
+ XCTAssertEqual(
100
+ result,
101
+ ["unauthenticated", "User is unauthenticated. Authenticate and try again."]
102
+ )
103
+ }
104
+
105
+ func testUnauthorized() {
106
+ let result = RNFBStorageErrorCodeMapper.codeAndMessage(
107
+ for: makeError(code: codeUnauthorized)
108
+ )
109
+ XCTAssertEqual(
110
+ result,
111
+ ["unauthorized", "User is not authorized to perform the desired action."]
112
+ )
113
+ }
114
+
115
+ func testRetryLimitExceeded() {
116
+ let result = RNFBStorageErrorCodeMapper.codeAndMessage(
117
+ for: makeError(code: codeRetryLimitExceeded)
118
+ )
119
+ XCTAssertEqual(
120
+ result,
121
+ [
122
+ "retry-limit-exceeded",
123
+ "The maximum time limit on an operation (upload, download, delete, etc.) has been exceeded.",
124
+ ]
125
+ )
126
+ }
127
+
128
+ func testNonMatchingChecksum() {
129
+ let result = RNFBStorageErrorCodeMapper.codeAndMessage(
130
+ for: makeError(code: codeNonMatchingChecksum)
131
+ )
132
+ XCTAssertEqual(
133
+ result,
134
+ [
135
+ "non-matching-checksum",
136
+ "File on the client does not match the checksum of the file received by the server.",
137
+ ]
138
+ )
139
+ }
140
+
141
+ func testDownloadSizeExceeded() {
142
+ let result = RNFBStorageErrorCodeMapper.codeAndMessage(
143
+ for: makeError(code: codeDownloadSizeExceeded)
144
+ )
145
+ XCTAssertEqual(
146
+ result,
147
+ [
148
+ "download-size-exceeded",
149
+ "Size of the downloaded file exceeds the amount of memory allocated for the download.",
150
+ ]
151
+ )
152
+ }
153
+
154
+ func testCancelled() {
155
+ let result = RNFBStorageErrorCodeMapper.codeAndMessage(
156
+ for: makeError(code: codeCancelled)
157
+ )
158
+ XCTAssertEqual(result, ["cancelled", "User cancelled the operation."])
159
+ }
160
+
161
+ func testUnknownWithoutExtras() {
162
+ let result = RNFBStorageErrorCodeMapper.codeAndMessage(
163
+ for: makeError(code: codeUnknown, description: "should be replaced")
164
+ )
165
+ XCTAssertEqual(result, ["unknown", "An unknown error has occurred."])
166
+ }
167
+
168
+ func testUnknownPermissionDeniedOverride() {
169
+ let underlying = NSError(
170
+ domain: NSPOSIXErrorDomain,
171
+ code: 1,
172
+ userInfo: [
173
+ NSLocalizedDescriptionKey: "The operation couldn’t be completed. Permission denied",
174
+ ]
175
+ )
176
+ let result = RNFBStorageErrorCodeMapper.codeAndMessage(
177
+ for: makeError(
178
+ code: codeUnknown,
179
+ userInfo: [NSUnderlyingErrorKey: underlying]
180
+ )
181
+ )
182
+ XCTAssertEqual(
183
+ result,
184
+ [
185
+ "invalid-device-file-path",
186
+ "The specified device file path is invalid or is restricted.",
187
+ ]
188
+ )
189
+ }
190
+
191
+ func testUnknownResponseBodyMessage() {
192
+ let result = RNFBStorageErrorCodeMapper.codeAndMessage(
193
+ for: makeError(
194
+ code: codeUnknown,
195
+ userInfo: ["ResponseBody": "quota soft limit"]
196
+ )
197
+ )
198
+ XCTAssertEqual(
199
+ result,
200
+ ["unknown", "An unknown error has occurred. (underlying reason 'quota soft limit')"]
201
+ )
202
+ }
203
+
204
+ func testDefaultKeepsLocalizedDescription() {
205
+ // FIRStorageErrorCodeInvalidArgument (-13050) and any unmapped code
206
+ let result = RNFBStorageErrorCodeMapper.codeAndMessage(
207
+ for: makeError(code: -13050, description: "Invalid argument from SDK")
208
+ )
209
+ XCTAssertEqual(result, ["unknown", "Invalid argument from SDK"])
210
+ }
211
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ #import <XCTest/XCTest.h>
18
+
19
+ #import "RNFBHandleMapStorage-Swift.inc"
20
+
21
+ @interface RNFBStorageErrorSnapshotDictBuilderFacadeTests : XCTestCase
22
+ @end
23
+
24
+ @implementation RNFBStorageErrorSnapshotDictBuilderFacadeTests
25
+
26
+ - (NSError *)errorWithCode:(NSInteger)code description:(NSString *)description {
27
+ return [NSError errorWithDomain:@"FIRStorageErrorDomain"
28
+ code:code
29
+ userInfo:@{NSLocalizedDescriptionKey : description}];
30
+ }
31
+
32
+ - (void)testFullBuilderSetsCodeMessageAndNativeErrorMessage {
33
+ NSMutableDictionary *snapshot = [@{@"status" : @"error"} mutableCopy];
34
+ // FIRStorageErrorCodeObjectNotFound
35
+ NSError *error = [self errorWithCode:-13010 description:@"native detail"];
36
+
37
+ NSDictionary *result = [RNFBStorageErrorSnapshotDictBuilder buildErrorSnapshotDict:error
38
+ taskSnapshotDict:snapshot];
39
+
40
+ XCTAssertTrue(result == snapshot, @"must mutate in place and return same dict");
41
+ NSDictionary *errorDict = snapshot[@"error"];
42
+ XCTAssertEqualObjects(errorDict[@"code"], @"object-not-found");
43
+ XCTAssertEqualObjects(errorDict[@"message"], @"No object exists at the desired reference.");
44
+ XCTAssertEqualObjects(errorDict[@"nativeErrorMessage"], @"native detail");
45
+ XCTAssertEqual(errorDict.count, 3u);
46
+ }
47
+
48
+ - (void)testFromCodeAndMessageSetsCodeAndMessageOnly {
49
+ NSMutableDictionary *snapshot = [@{@"status" : @"error"} mutableCopy];
50
+ NSArray *pair = @[ @"unauthorized", @"User is not authorized to perform the desired action." ];
51
+
52
+ NSDictionary *result =
53
+ [RNFBStorageErrorSnapshotDictBuilder buildErrorSnapshotDictFromCodeAndMessage:pair
54
+ taskSnapshotDict:snapshot];
55
+
56
+ XCTAssertTrue(result == snapshot, @"must mutate in place and return same dict");
57
+ NSDictionary *errorDict = snapshot[@"error"];
58
+ XCTAssertEqualObjects(errorDict[@"code"], @"unauthorized");
59
+ XCTAssertEqualObjects(errorDict[@"message"],
60
+ @"User is not authorized to perform the desired action.");
61
+ XCTAssertNil(errorDict[@"nativeErrorMessage"]);
62
+ XCTAssertEqual(errorDict.count, 2u);
63
+ }
64
+
65
+ - (void)testNilSnapshotDictReturnsNil {
66
+ NSError *error = [self errorWithCode:-13040 description:@"cancelled native"];
67
+ NSDictionary *fullNil = [RNFBStorageErrorSnapshotDictBuilder buildErrorSnapshotDict:error
68
+ taskSnapshotDict:nil];
69
+ XCTAssertNil(fullNil);
70
+
71
+ NSArray *pair = @[ @"cancelled", @"User cancelled the operation." ];
72
+ NSDictionary *codeNil =
73
+ [RNFBStorageErrorSnapshotDictBuilder buildErrorSnapshotDictFromCodeAndMessage:pair
74
+ taskSnapshotDict:nil];
75
+ XCTAssertNil(codeNil);
76
+ }
77
+
78
+ - (void)testFullBuilderNilErrorUsesNullNativeMessage {
79
+ NSMutableDictionary *snapshot = [NSMutableDictionary dictionary];
80
+ NSDictionary *result = [RNFBStorageErrorSnapshotDictBuilder buildErrorSnapshotDict:nil
81
+ taskSnapshotDict:snapshot];
82
+ XCTAssertTrue(result == snapshot);
83
+ NSDictionary *errorDict = snapshot[@"error"];
84
+ XCTAssertEqualObjects(errorDict[@"code"], @"unknown");
85
+ XCTAssertEqualObjects(errorDict[@"message"], @"An unknown error has occurred.");
86
+ XCTAssertEqualObjects(errorDict[@"nativeErrorMessage"], [NSNull null]);
87
+ }
88
+
89
+ - (void)testEmptySnapshotDictMutatedInPlace {
90
+ NSMutableDictionary *empty = [NSMutableDictionary dictionary];
91
+ NSError *error = [self errorWithCode:-13040 description:@"cancelled native"];
92
+
93
+ NSDictionary *fullResult = [RNFBStorageErrorSnapshotDictBuilder buildErrorSnapshotDict:error
94
+ taskSnapshotDict:empty];
95
+ XCTAssertTrue(fullResult == empty);
96
+ XCTAssertEqualObjects(empty[@"error"][@"code"], @"cancelled");
97
+ XCTAssertEqualObjects(empty[@"error"][@"nativeErrorMessage"], @"cancelled native");
98
+
99
+ NSMutableDictionary *empty2 = [NSMutableDictionary dictionary];
100
+ NSArray *pair = @[ @"unknown", @"An unknown error has occurred." ];
101
+ NSDictionary *codeResult =
102
+ [RNFBStorageErrorSnapshotDictBuilder buildErrorSnapshotDictFromCodeAndMessage:pair
103
+ taskSnapshotDict:empty2];
104
+ XCTAssertTrue(codeResult == empty2);
105
+ XCTAssertEqualObjects(empty2[@"error"][@"code"], @"unknown");
106
+ XCTAssertNil(empty2[@"error"][@"nativeErrorMessage"]);
107
+ }
108
+
109
+ - (void)testFacadePublicSelectorsUnchanged {
110
+ SEL fullSel = @selector(buildErrorSnapshotDict:taskSnapshotDict:);
111
+ SEL fromCodeSel = @selector(buildErrorSnapshotDictFromCodeAndMessage:taskSnapshotDict:);
112
+
113
+ XCTAssertTrue([RNFBStorageErrorSnapshotDictBuilder respondsToSelector:fullSel]);
114
+ XCTAssertTrue([RNFBStorageErrorSnapshotDictBuilder respondsToSelector:fromCodeSel]);
115
+
116
+ NSMethodSignature *fullSig =
117
+ [RNFBStorageErrorSnapshotDictBuilder methodSignatureForSelector:fullSel];
118
+ XCTAssertEqual(strcmp(fullSig.methodReturnType, @encode(id)), 0);
119
+
120
+ NSMethodSignature *codeSig =
121
+ [RNFBStorageErrorSnapshotDictBuilder methodSignatureForSelector:fromCodeSel];
122
+ XCTAssertEqual(strcmp(codeSig.methodReturnType, @encode(id)), 0);
123
+ }
124
+
125
+ @end
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import XCTest
18
+
19
+ private final class FakeCancellableHandle: NSObject {
20
+ private(set) var cancelCount = 0
21
+ var onCancel: (() -> Void)?
22
+
23
+ @objc func cancel() {
24
+ cancelCount += 1
25
+ onCancel?()
26
+ }
27
+ }
28
+
29
+ final class RNFBStorageTaskRegistryStorageTests: XCTestCase {
30
+ private var storage: RNFBStorageTaskRegistryStorage!
31
+
32
+ override func setUp() {
33
+ super.setUp()
34
+ storage = RNFBStorageTaskRegistryStorage()
35
+ }
36
+
37
+ func testPutIfAbsent_storesWhenFree() {
38
+ let handle = FakeCancellableHandle()
39
+ XCTAssertTrue(storage.putIfAbsent(1 as NSNumber, value: handle))
40
+ XCTAssertTrue(storage.get(1 as NSNumber) === handle)
41
+ XCTAssertEqual(handle.cancelCount, 0)
42
+ }
43
+
44
+ func testPutIfAbsent_collision_keepsExistingWithoutCancel() {
45
+ let first = FakeCancellableHandle()
46
+ let second = FakeCancellableHandle()
47
+ XCTAssertTrue(storage.putIfAbsent(1 as NSNumber, value: first))
48
+ XCTAssertFalse(storage.putIfAbsent(1 as NSNumber, value: second))
49
+ XCTAssertTrue(storage.get(1 as NSNumber) === first)
50
+ XCTAssertEqual(first.cancelCount, 0)
51
+ XCTAssertEqual(second.cancelCount, 0)
52
+ }
53
+
54
+ func testPutOrDiscard_storesWhenFree() {
55
+ let handle = FakeCancellableHandle()
56
+ XCTAssertTrue(storage.putOrDiscard(1 as NSNumber, value: handle))
57
+ XCTAssertTrue(storage.get(1 as NSNumber) === handle)
58
+ XCTAssertEqual(handle.cancelCount, 0)
59
+ }
60
+
61
+ func testPutOrDiscard_collision_cancelsIncoming() {
62
+ let first = FakeCancellableHandle()
63
+ let duplicate = FakeCancellableHandle()
64
+ XCTAssertTrue(storage.putOrDiscard(1 as NSNumber, value: first))
65
+ XCTAssertFalse(storage.putOrDiscard(1 as NSNumber, value: duplicate))
66
+ XCTAssertEqual(duplicate.cancelCount, 1)
67
+ XCTAssertEqual(first.cancelCount, 0)
68
+ XCTAssertTrue(storage.get(1 as NSNumber) === first)
69
+ }
70
+
71
+ func testGet_whenFree_isNil() {
72
+ XCTAssertNil(storage.get(99 as NSNumber))
73
+ }
74
+
75
+ func testTake_removesAndReturnsWithoutCallingCancel() {
76
+ let handle = FakeCancellableHandle()
77
+ XCTAssertTrue(storage.putIfAbsent(1 as NSNumber, value: handle))
78
+ XCTAssertTrue(storage.take(1 as NSNumber) === handle)
79
+ XCTAssertNil(storage.get(1 as NSNumber))
80
+ XCTAssertEqual(handle.cancelCount, 0)
81
+ }
82
+
83
+ func testTake_whenFree_isNil() {
84
+ XCTAssertNil(storage.take(99 as NSNumber))
85
+ }
86
+
87
+ func testTakeIf_whenIdentityMatches_takes() {
88
+ let handle = FakeCancellableHandle()
89
+ XCTAssertTrue(storage.putIfAbsent(1 as NSNumber, value: handle))
90
+ XCTAssertTrue(storage.takeIf(1 as NSNumber, when: { $0 === handle }) === handle)
91
+ XCTAssertNil(storage.get(1 as NSNumber))
92
+ }
93
+
94
+ func testTakeIf_whenIdentityMismatch_leavesMapping() {
95
+ let mapped = FakeCancellableHandle()
96
+ let other = FakeCancellableHandle()
97
+ XCTAssertTrue(storage.putIfAbsent(1 as NSNumber, value: mapped))
98
+ XCTAssertNil(storage.takeIf(1 as NSNumber, when: { $0 === other }))
99
+ XCTAssertTrue(storage.get(1 as NSNumber) === mapped)
100
+ }
101
+
102
+ func testTakeAndCancel_cancelsAndRemoves() {
103
+ let handle = FakeCancellableHandle()
104
+ XCTAssertTrue(storage.putIfAbsent(1 as NSNumber, value: handle))
105
+ XCTAssertTrue(storage.takeAndCancel(1 as NSNumber))
106
+ XCTAssertEqual(handle.cancelCount, 1)
107
+ XCTAssertNil(storage.get(1 as NSNumber))
108
+ }
109
+
110
+ func testTakeAndCancel_missingKey_returnsFalse() {
111
+ XCTAssertFalse(storage.takeAndCancel(99 as NSNumber))
112
+ }
113
+
114
+ func testTakeAndCancel_replacementDuringCancel_leavesReplacement() {
115
+ let original = FakeCancellableHandle()
116
+ let replacement = FakeCancellableHandle()
117
+ original.onCancel = { [weak self] in
118
+ guard let self = self else { return }
119
+ _ = self.storage.take(11 as NSNumber)
120
+ _ = self.storage.putIfAbsent(11 as NSNumber, value: replacement)
121
+ }
122
+ XCTAssertTrue(storage.putIfAbsent(11 as NSNumber, value: original))
123
+ XCTAssertTrue(storage.takeAndCancel(11 as NSNumber))
124
+ XCTAssertEqual(original.cancelCount, 1)
125
+ XCTAssertTrue(storage.get(11 as NSNumber) === replacement)
126
+ XCTAssertEqual(replacement.cancelCount, 0)
127
+ }
128
+
129
+ func testTakeAndCancel_objectWithoutCancel_doesNotCrash() {
130
+ let plain = NSObject()
131
+ XCTAssertTrue(storage.putIfAbsent(1 as NSNumber, value: plain))
132
+ XCTAssertTrue(storage.takeAndCancel(1 as NSNumber))
133
+ XCTAssertNil(storage.get(1 as NSNumber))
134
+ }
135
+
136
+ func testCancelAll_cancelsSnapshotAndLeavesEmpty() {
137
+ let a = FakeCancellableHandle()
138
+ let b = FakeCancellableHandle()
139
+ XCTAssertTrue(storage.putIfAbsent(1 as NSNumber, value: a))
140
+ XCTAssertTrue(storage.putIfAbsent(2 as NSNumber, value: b))
141
+ storage.cancelAll()
142
+ XCTAssertEqual(a.cancelCount, 1)
143
+ XCTAssertEqual(b.cancelCount, 1)
144
+ XCTAssertNil(storage.get(1 as NSNumber))
145
+ XCTAssertNil(storage.get(2 as NSNumber))
146
+ }
147
+
148
+ func testCancelAll_objectWithoutCancel_doesNotCrash() {
149
+ let plain = NSObject()
150
+ XCTAssertTrue(storage.putIfAbsent(1 as NSNumber, value: plain))
151
+ storage.cancelAll()
152
+ XCTAssertNil(storage.get(1 as NSNumber))
153
+ }
154
+ }