Skip to content

Commit 366cdf0

Browse files
committed
test: fix issue with HTTPCookieStorage in tests
1 parent 08b92db commit 366cdf0

7 files changed

Lines changed: 51 additions & 126 deletions

File tree

android/src/main/java/com/mendixnative/cookie/MxCookieModule.kt

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,7 @@ class MxCookieModule(reactContext: ReactApplicationContext) :
2222
// The following methods are iOS-only (keychain / SessionCookieStore).
2323
// They are no-ops on Android so the TurboModule spec is satisfied.
2424

25-
override fun seedTestCookies(count: Double, valueSize: Double, promise: Promise) {
26-
promise.resolve(null)
27-
}
28-
29-
override fun persistSessionCookies(promise: Promise) {
30-
promise.resolve(null)
31-
}
32-
33-
override fun clearHTTPCookies(promise: Promise) {
25+
override fun persistTestCookies(count: Double, valueSize: Double, promise: Promise) {
3426
promise.resolve(null)
3527
}
3628

example/__tests__/session-cookie-store.harness.ts

Lines changed: 15 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,10 @@ describe('SessionCookieStore', () => {
2020

2121
describe('small-blob round-trip (single-item format)', () => {
2222
test('persists and restores small cookies', async () => {
23-
await NativeCookieTestHelpers.seedTestCookies(
23+
await NativeCookieTestHelpers.persistTestCookies(
2424
SMALL_COUNT,
2525
SMALL_VALUE_SIZE
2626
);
27-
await NativeCookieTestHelpers.persistSessionCookies();
28-
await NativeCookieTestHelpers.clearHTTPCookies();
2927

3028
const names = await NativeCookieTestHelpers.restoreSessionCookies();
3129

@@ -36,28 +34,24 @@ describe('SessionCookieStore', () => {
3634
});
3735

3836
test('single-item write does not create a chunk commit-marker', async () => {
39-
await NativeCookieTestHelpers.seedTestCookies(
37+
await NativeCookieTestHelpers.persistTestCookies(
4038
SMALL_COUNT,
4139
SMALL_VALUE_SIZE
4240
);
43-
await NativeCookieTestHelpers.persistSessionCookies();
4441

4542
const chunkCount = await NativeCookieTestHelpers.getKeychainChunkCount();
4643

4744
expect(chunkCount).toBe(0);
4845
});
4946

5047
test('keychain is empty after restore (cleared on read)', async () => {
51-
await NativeCookieTestHelpers.seedTestCookies(
48+
await NativeCookieTestHelpers.persistTestCookies(
5249
SMALL_COUNT,
5350
SMALL_VALUE_SIZE
5451
);
55-
await NativeCookieTestHelpers.persistSessionCookies();
56-
await NativeCookieTestHelpers.clearHTTPCookies();
5752
await NativeCookieTestHelpers.restoreSessionCookies();
5853

5954
// A second restore should find nothing.
60-
await NativeCookieTestHelpers.clearHTTPCookies();
6155
const names = await NativeCookieTestHelpers.restoreSessionCookies();
6256

6357
expect(names.length).toBe(0);
@@ -70,12 +64,10 @@ describe('SessionCookieStore', () => {
7064

7165
describe('large-blob round-trip (chunked format)', () => {
7266
test('persists and restores large cookies', async () => {
73-
await NativeCookieTestHelpers.seedTestCookies(
67+
await NativeCookieTestHelpers.persistTestCookies(
7468
LARGE_COUNT,
7569
LARGE_VALUE_SIZE
7670
);
77-
await NativeCookieTestHelpers.persistSessionCookies();
78-
await NativeCookieTestHelpers.clearHTTPCookies();
7971

8072
const names = await NativeCookieTestHelpers.restoreSessionCookies();
8173

@@ -86,24 +78,21 @@ describe('SessionCookieStore', () => {
8678
});
8779

8880
test('chunked write creates a commit-marker with count > 1', async () => {
89-
await NativeCookieTestHelpers.seedTestCookies(
81+
await NativeCookieTestHelpers.persistTestCookies(
9082
LARGE_COUNT,
9183
LARGE_VALUE_SIZE
9284
);
93-
await NativeCookieTestHelpers.persistSessionCookies();
9485

9586
const chunkCount = await NativeCookieTestHelpers.getKeychainChunkCount();
9687

9788
expect(chunkCount).toBeGreaterThan(1);
9889
});
9990

10091
test('commit-marker is removed after restore (chunked keys cleared on read)', async () => {
101-
await NativeCookieTestHelpers.seedTestCookies(
92+
await NativeCookieTestHelpers.persistTestCookies(
10293
LARGE_COUNT,
10394
LARGE_VALUE_SIZE
10495
);
105-
await NativeCookieTestHelpers.persistSessionCookies();
106-
await NativeCookieTestHelpers.clearHTTPCookies();
10796
await NativeCookieTestHelpers.restoreSessionCookies();
10897

10998
const chunkCount = await NativeCookieTestHelpers.getKeychainChunkCount();
@@ -112,15 +101,12 @@ describe('SessionCookieStore', () => {
112101
});
113102

114103
test('keychain is empty after restore (no second restore possible)', async () => {
115-
await NativeCookieTestHelpers.seedTestCookies(
104+
await NativeCookieTestHelpers.persistTestCookies(
116105
LARGE_COUNT,
117106
LARGE_VALUE_SIZE
118107
);
119-
await NativeCookieTestHelpers.persistSessionCookies();
120-
await NativeCookieTestHelpers.clearHTTPCookies();
121108
await NativeCookieTestHelpers.restoreSessionCookies();
122109

123-
await NativeCookieTestHelpers.clearHTTPCookies();
124110
const names = await NativeCookieTestHelpers.restoreSessionCookies();
125111

126112
expect(names.length).toBe(0);
@@ -133,45 +119,36 @@ describe('SessionCookieStore', () => {
133119

134120
describe('format transitions', () => {
135121
test('overwriting large (chunked) with small (single-item) leaves no chunk marker', async () => {
136-
await NativeCookieTestHelpers.seedTestCookies(
122+
await NativeCookieTestHelpers.persistTestCookies(
137123
LARGE_COUNT,
138124
LARGE_VALUE_SIZE
139125
);
140-
await NativeCookieTestHelpers.persistSessionCookies();
141126

142-
// Replace with a small set.
143-
await NativeCookieTestHelpers.clearHTTPCookies();
144-
await NativeCookieTestHelpers.seedTestCookies(
127+
// Overwrite with a small set.
128+
await NativeCookieTestHelpers.persistTestCookies(
145129
SMALL_COUNT,
146130
SMALL_VALUE_SIZE
147131
);
148-
await NativeCookieTestHelpers.persistSessionCookies();
149132

150133
const chunkCount = await NativeCookieTestHelpers.getKeychainChunkCount();
151134
expect(chunkCount).toBe(0);
152135

153-
// Data is still correct.
154-
await NativeCookieTestHelpers.clearHTTPCookies();
155136
const names = await NativeCookieTestHelpers.restoreSessionCookies();
156137
expect(names.length).toBe(SMALL_COUNT);
157138
});
158139

159140
test('overwriting small (single-item) with large (chunked) round-trips correctly', async () => {
160-
await NativeCookieTestHelpers.seedTestCookies(
141+
await NativeCookieTestHelpers.persistTestCookies(
161142
SMALL_COUNT,
162143
SMALL_VALUE_SIZE
163144
);
164-
await NativeCookieTestHelpers.persistSessionCookies();
165145

166-
// Replace with a large set.
167-
await NativeCookieTestHelpers.clearHTTPCookies();
168-
await NativeCookieTestHelpers.seedTestCookies(
146+
// Overwrite with a large set.
147+
await NativeCookieTestHelpers.persistTestCookies(
169148
LARGE_COUNT,
170149
LARGE_VALUE_SIZE
171150
);
172-
await NativeCookieTestHelpers.persistSessionCookies();
173151

174-
await NativeCookieTestHelpers.clearHTTPCookies();
175152
const names = await NativeCookieTestHelpers.restoreSessionCookies();
176153
expect(names.length).toBe(LARGE_COUNT);
177154
});
@@ -183,30 +160,26 @@ describe('SessionCookieStore', () => {
183160

184161
describe('clearAll', () => {
185162
test('removes cookies after a small-blob persist', async () => {
186-
await NativeCookieTestHelpers.seedTestCookies(
163+
await NativeCookieTestHelpers.persistTestCookies(
187164
SMALL_COUNT,
188165
SMALL_VALUE_SIZE
189166
);
190-
await NativeCookieTestHelpers.persistSessionCookies();
191167
await NativeCookie.clearAll();
192168

193-
await NativeCookieTestHelpers.clearHTTPCookies();
194169
const names = await NativeCookieTestHelpers.restoreSessionCookies();
195170
expect(names.length).toBe(0);
196171
});
197172

198173
test('removes cookies and chunk marker after a large-blob persist', async () => {
199-
await NativeCookieTestHelpers.seedTestCookies(
174+
await NativeCookieTestHelpers.persistTestCookies(
200175
LARGE_COUNT,
201176
LARGE_VALUE_SIZE
202177
);
203-
await NativeCookieTestHelpers.persistSessionCookies();
204178
await NativeCookie.clearAll();
205179

206180
const chunkCount = await NativeCookieTestHelpers.getKeychainChunkCount();
207181
expect(chunkCount).toBe(0);
208182

209-
await NativeCookieTestHelpers.clearHTTPCookies();
210183
const names = await NativeCookieTestHelpers.restoreSessionCookies();
211184
expect(names.length).toBe(0);
212185
});

ios/Modules/NativeCookieModule/NativeCookieModule.swift

Lines changed: 6 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -20,47 +20,16 @@ public class NativeCookieModule: NSObject {
2020
// session DoS, and keychain information disclosure from arbitrary JS callers.
2121

2222
#if DEBUG
23-
/// Seeds `count` session cookies (no expiry), each with a `valueSize`-byte value,
24-
/// into `HTTPCookieStorage.shared`. Used in harness tests to produce blobs of
25-
/// controlled size without relying on a live server.
26-
public func seedTestCookies(count: Int, valueSize: Int, promise: Promise) {
27-
let storage = HTTPCookieStorage.shared
28-
for i in 0..<count {
29-
if let cookie = HTTPCookie(properties: [
30-
.name: "testCookie\(i)",
31-
.value: String(repeating: "X", count: valueSize),
32-
.domain: "test.mendix.com",
33-
.path: "/",
34-
// no .expires → session cookie (expiresDate == nil)
35-
]) {
36-
storage.setCookie(cookie)
37-
}
38-
}
39-
promise.resolve(nil)
40-
}
41-
42-
/// Persists the current session cookies in `HTTPCookieStorage.shared` to the keychain
43-
/// and resolves the promise once the async write completes.
44-
public func persistSessionCookies(_ promise: Promise) {
45-
SessionCookieStore.persist(completion: {
23+
/// Writes `count` synthetic session cookies with `valueSize`-byte values directly
24+
/// to the keychain (bypasses HTTPCookieStorage) and resolves once the write completes.
25+
public func persistTestCookies(count: Int, valueSize: Int, promise: Promise) {
26+
SessionCookieStore.persistTestCookies(count: count, valueSize: valueSize) {
4627
promise.resolve(nil)
47-
})
48-
}
49-
50-
/// Deletes all cookies from `HTTPCookieStorage.shared` without touching the keychain.
51-
/// Use this between `persistSessionCookies` and `restoreSessionCookies` to simulate
52-
/// an app restart.
53-
public func clearHTTPCookies(_ promise: Promise) {
54-
let storage = HTTPCookieStorage.shared
55-
(storage.cookies ?? []).forEach { storage.deleteCookie($0) }
56-
promise.resolve(nil)
28+
}
5729
}
5830

59-
/// Calls `SessionCookieStore.restore()` and returns the names of every cookie
60-
/// currently in `HTTPCookieStorage.shared` after the restore.
6131
public func restoreSessionCookies(_ promise: Promise) {
62-
SessionCookieStore.restore()
63-
let names = (HTTPCookieStorage.shared.cookies ?? []).map(\.name)
32+
let names = SessionCookieStore.restoreTestCookieNames()
6433
promise.resolve(names)
6534
}
6635

ios/Modules/NativeCookieModule/SessionCookieStore.swift

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,30 @@ public class SessionCookieStore {
3737
}
3838

3939
#if DEBUG
40-
/// Persist variant that calls `completion` once the keychain write finishes.
41-
/// Only compiled in DEBUG builds; used by the harness test bridge to await completion.
42-
public static func persist(completion: @escaping () -> Void) {
40+
/// Writes `count` synthetic session cookies with `valueSize`-byte values directly
41+
/// to the keychain, bypassing HTTPCookieStorage. Resolves via `completion` once
42+
/// the async queue write finishes. Used by harness tests only.
43+
public static func persistTestCookies(count: Int, valueSize: Int, completion: @escaping () -> Void) {
4344
queue.async {
44-
let sessionCookies = HTTPCookieStorage.shared.cookies?.filter { isSessionCookie($0) } ?? []
45-
guard !sessionCookies.isEmpty else {
46-
clear()
47-
NSLog("SessionCookieStore: Clear existing session cookies from storage")
48-
completion()
49-
return
45+
let cookies = (0..<count).compactMap { i in
46+
HTTPCookie(properties: [
47+
.name: "testCookie\(i)",
48+
.value: String(repeating: "X", count: valueSize),
49+
.domain: "test.mendix.com",
50+
.path: "/",
51+
])
5052
}
51-
set(key: storageKey, cookies: sessionCookies)
53+
guard !cookies.isEmpty else { completion(); return }
54+
set(key: storageKey, cookies: cookies)
5255
completion()
5356
}
5457
}
58+
59+
public static func restoreTestCookieNames() -> [String] {
60+
guard let cookies = get(key: storageKey) else { return [] }
61+
clear()
62+
return cookies.map(\.name)
63+
}
5564
#endif
5665

5766
public static func clear() {

ios/TurboModules/MxCookie/MxCookie.mm

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,13 @@ - (void)clearAll:(nonnull RCTPromiseResolveBlock)resolve
2020
}
2121

2222
#if DEBUG
23-
- (void)seedTestCookies:(double)count valueSize:(double)valueSize
24-
resolve:(nonnull RCTPromiseResolveBlock)resolve
25-
reject:(nonnull RCTPromiseRejectBlock)reject {
23+
- (void)persistTestCookies:(double)count valueSize:(double)valueSize
24+
resolve:(nonnull RCTPromiseResolveBlock)resolve
25+
reject:(nonnull RCTPromiseRejectBlock)reject {
2626
Promise *promise = [Promise instance:resolve reject:reject];
27-
[[[NativeCookieModule alloc] init] seedTestCookiesWithCount:(NSInteger)count
28-
valueSize:(NSInteger)valueSize
29-
promise:promise];
30-
}
31-
32-
- (void)persistSessionCookies:(nonnull RCTPromiseResolveBlock)resolve
33-
reject:(nonnull RCTPromiseRejectBlock)reject {
34-
Promise *promise = [Promise instance:resolve reject:reject];
35-
[[[NativeCookieModule alloc] init] persistSessionCookies:promise];
36-
}
37-
38-
- (void)clearHTTPCookies:(nonnull RCTPromiseResolveBlock)resolve
39-
reject:(nonnull RCTPromiseRejectBlock)reject {
40-
Promise *promise = [Promise instance:resolve reject:reject];
41-
[[[NativeCookieModule alloc] init] clearHTTPCookies:promise];
27+
[[[NativeCookieModule alloc] init] persistTestCookiesWithCount:(NSInteger)count
28+
valueSize:(NSInteger)valueSize
29+
promise:promise];
4230
}
4331

4432
- (void)restoreSessionCookies:(nonnull RCTPromiseResolveBlock)resolve

src/cookie/NativeMxCookie.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ export interface Spec extends TurboModule {
44
clearAll(): Promise<void>;
55
// The following methods are only implemented in DEBUG builds.
66
// They must not be called in production; doing so will throw a TurboModule lookup error.
7-
seedTestCookies(count: number, valueSize: number): Promise<void>;
8-
persistSessionCookies(): Promise<void>;
9-
clearHTTPCookies(): Promise<void>;
7+
persistTestCookies(count: number, valueSize: number): Promise<void>;
108
restoreSessionCookies(): Promise<string[]>;
119
getKeychainChunkCount(): Promise<number>;
1210
}

src/cookie/index.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,9 @@ export const NativeCookie = {
1010
* from release binaries and calls will throw a TurboModule lookup error.
1111
*/
1212
export const NativeCookieTestHelpers = {
13-
/** Seeds session cookies into HTTPCookieStorage. */
14-
seedTestCookies: NativeMxCookie.seedTestCookies,
15-
/** Persists current session cookies to the keychain. Resolves when the write completes. */
16-
persistSessionCookies: NativeMxCookie.persistSessionCookies,
17-
/** Clears all cookies from HTTPCookieStorage without touching the keychain (simulates an app restart). */
18-
clearHTTPCookies: NativeMxCookie.clearHTTPCookies,
19-
/** Restores cookies from the keychain into HTTPCookieStorage and returns their names. */
13+
/** Writes N synthetic session cookies directly to the keychain (bypasses HTTPCookieStorage). Resolves when the write completes. */
14+
persistTestCookies: NativeMxCookie.persistTestCookies,
15+
/** Reads cookies directly from the keychain, clears the entry, and returns their names (bypasses HTTPCookieStorage). */
2016
restoreSessionCookies: NativeMxCookie.restoreSessionCookies,
2117
/** Returns the _chunkcount commit-marker value (> 1 = chunked write; 0 = single-item or empty). */
2218
getKeychainChunkCount: NativeMxCookie.getKeychainChunkCount,

0 commit comments

Comments
 (0)