From 046269f1df176379bd9b0ed99ee550c6676ed847 Mon Sep 17 00:00:00 2001
From: Morgan Pretty <morgan.t.pretty@gmail.com>
Date: Fri, 18 Mar 2022 10:15:57 +1100
Subject: [PATCH] Finished the OpenGroupManager unit tests

Fixed a bug with how the open group URL processing was working (one of the example URLs wasn't getting processed correctly)
---
 .../Open Groups/OpenGroupManager.swift        |   7 +-
 .../Open Groups/OpenGroupManagerSpec.swift    | 487 ++++++++++++++++--
 2 files changed, 439 insertions(+), 55 deletions(-)

diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift
index f9726f1aa..eeb57c317 100644
--- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift	
+++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift	
@@ -620,7 +620,7 @@ public final class OpenGroupManager: NSObject {
                 dependencies.storage.write { transaction in
                     dependencies.storage.setOpenGroupImage(to: imageData, for: roomToken, on: server, using: transaction)
                 }
-                UserDefaults.standard[.lastOpenGroupImageUpdate] = now
+                dependencies.standardUserDefaults[.lastOpenGroupImageUpdate] = now
             }
         }
         dependencies.mutableCache.mutate { cache in
@@ -644,7 +644,10 @@ public final class OpenGroupManager: NSObject {
         // 143.198.213.255:80/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c
         // 143.198.213.255:80/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c
         let useTLS = (url.scheme == "https")
-        let updatedPath = (url.path.starts(with: "/r/") ? url.path.substring(from: 2) : url.path)
+        
+        // If there is no scheme then the host is included in the path (so handle that case)
+        let hostFreePath = (url.host != nil ? url.path : url.path.substring(from: host.count))
+        let updatedPath = (hostFreePath.starts(with: "/r/") ? hostFreePath.substring(from: 2) : hostFreePath)
         let room = String(updatedPath.dropFirst()) // Drop the leading slash
         let queryParts = query.split(separator: "=")
         guard !room.isEmpty && !room.contains("/"), queryParts.count == 2, queryParts[0] == "public_key" else { return nil }
diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift
index 53410214b..c7c7b863a 100644
--- a/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift	
+++ b/SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift	
@@ -514,7 +514,7 @@ class OpenGroupManagerSpec: QuickSpec {
                         .map { _ -> Void in didComplete = true }
                         .retainUntilComplete()
                     
-                    expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                    expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                     expect(mockStorage)
                         .to(
                             call(.exactly(times: 1)) {
@@ -542,7 +542,7 @@ class OpenGroupManagerSpec: QuickSpec {
                         .map { _ -> Void in didComplete = true }
                         .retainUntilComplete()
                     
-                    expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                    expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                     expect(mockStorage)
                         .to(
                             call(.exactly(times: 1)) {
@@ -570,13 +570,13 @@ class OpenGroupManagerSpec: QuickSpec {
                         .map { _ -> Void in didComplete = true }
                         .retainUntilComplete()
                     
-                    expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                    expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                     expect(mockOGMCache)
                         .toEventually(
                             call(matchingParameters: true) {
                                 $0.pollers = ["testServer": OpenGroupAPI.Poller(for: "testServer")]
                             },
-                            timeout: .milliseconds(100)
+                            timeout: .milliseconds(50)
                         )
                 }
                 
@@ -600,7 +600,7 @@ class OpenGroupManagerSpec: QuickSpec {
                             .map { _ -> Void in didComplete = true }
                             .retainUntilComplete()
                         
-                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                         expect(mockStorage)
                             .toEventuallyNot(
                                 call {
@@ -610,7 +610,7 @@ class OpenGroupManagerSpec: QuickSpec {
                                         using: testTransaction! as Any
                                     )
                                 },
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                         expect(mockStorage)
                             .toEventuallyNot(
@@ -621,7 +621,7 @@ class OpenGroupManagerSpec: QuickSpec {
                                         using: testTransaction! as Any
                                     )
                                 },
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                     }
                 }
@@ -656,7 +656,7 @@ class OpenGroupManagerSpec: QuickSpec {
                         expect(error?.localizedDescription)
                             .toEventually(
                                 equal(HTTP.Error.parsingFailed.localizedDescription),
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                     }
                 }
@@ -941,7 +941,7 @@ class OpenGroupManagerSpec: QuickSpec {
                         dependencies: dependencies
                     ) { didComplete = true }
                     
-                    expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                    expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                     expect(testGroupThread.numSaveCalls).to(equal(1))
                 }
                 
@@ -957,7 +957,7 @@ class OpenGroupManagerSpec: QuickSpec {
                         dependencies: dependencies
                     ) { didComplete = true }
                     
-                    expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                    expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                     expect(mockStorage).to(call { $0.getOpenGroup(for: any()) })
                 }
                 
@@ -973,7 +973,7 @@ class OpenGroupManagerSpec: QuickSpec {
                         dependencies: dependencies
                     ) { didComplete = true }
                     
-                    expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                    expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                     expect(testGroupThread.numSaveCalls).to(equal(1))
                 }
                 
@@ -989,7 +989,7 @@ class OpenGroupManagerSpec: QuickSpec {
                         dependencies: dependencies
                     ) { didComplete = true }
                     
-                    expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                    expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                     expect(mockStorage).to(call { $0.setOpenGroup(any(), for: any(), using: anyAny()) })
                 }
                 
@@ -1005,7 +1005,7 @@ class OpenGroupManagerSpec: QuickSpec {
                         dependencies: dependencies
                     ) { didComplete = true }
                     
-                    expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                    expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                     expect(mockStorage)
                         .to(call(matchingParameters: true) {
                             $0.setUserCount(to: 10, forOpenGroupWithID: "testServer.testRoom", using: testTransaction! as Any)
@@ -1029,7 +1029,7 @@ class OpenGroupManagerSpec: QuickSpec {
                     expect(didCallComplete)
                         .toEventually(
                             beTrue(),
-                            timeout: .milliseconds(100)
+                            timeout: .milliseconds(50)
                         )
                 }
                 
@@ -1064,13 +1064,13 @@ class OpenGroupManagerSpec: QuickSpec {
                             dependencies: dependencies
                         ) { didComplete = true }
                         
-                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                         expect(mockOGMCache)
                             .toEventually(
                                 call(matchingParameters: true) {
                                     $0.moderators = ["testServer": ["testRoom": Set(arrayLiteral: "TestMod")]]
                                 },
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                     }
                     
@@ -1104,13 +1104,13 @@ class OpenGroupManagerSpec: QuickSpec {
                             dependencies: dependencies
                         ) { didComplete = true }
                         
-                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                         expect(mockOGMCache)
                             .toEventually(
                                 call(matchingParameters: true) {
                                     $0.moderators = ["testServer": ["testRoom": Set()]]
                                 },
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                     }
                 }
@@ -1146,13 +1146,13 @@ class OpenGroupManagerSpec: QuickSpec {
                             dependencies: dependencies
                         ) { didComplete = true }
                         
-                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                         expect(mockOGMCache)
                             .toEventually(
                                 call(matchingParameters: true) {
                                     $0.admins = ["testServer": ["testRoom": Set(arrayLiteral: "TestAdmin")]]
                                 },
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                     }
                     
@@ -1186,13 +1186,13 @@ class OpenGroupManagerSpec: QuickSpec {
                             dependencies: dependencies
                         ) { didComplete = true }
                         
-                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                         expect(mockOGMCache)
                             .toEventually(
                                 call(matchingParameters: true) {
                                     $0.admins = ["testServer": ["testRoom": Set()]]
                                 },
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                     }
                 }
@@ -1227,7 +1227,7 @@ class OpenGroupManagerSpec: QuickSpec {
                             dependencies: dependencies
                         ) { didComplete = true }
                         
-                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                         expect(mockStorage)
                             .to(call(matchingParameters: true) {
                                 $0.setOpenGroup(
@@ -1295,7 +1295,7 @@ class OpenGroupManagerSpec: QuickSpec {
                             dependencies: dependencies
                         ) { didComplete = true }
                         
-                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                         expect(mockStorage)
                             .to(call(matchingParameters: true) {
                                 $0.setOpenGroup(
@@ -1330,7 +1330,7 @@ class OpenGroupManagerSpec: QuickSpec {
                             dependencies: dependencies
                         ) { didComplete = true }
                         
-                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                         expect(mockOGMCache)
                             .to(call(matchingParameters: true) {
                                 $0.pollers = ["testServer": OpenGroupAPI.Poller(for: "testServer")]
@@ -1351,7 +1351,7 @@ class OpenGroupManagerSpec: QuickSpec {
                             dependencies: dependencies
                         ) { didComplete = true }
                         
-                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                         expect(mockOGMCache).to(call(.exactly(times: 1)) { $0.pollers })
                     }
                 }
@@ -1421,7 +1421,7 @@ class OpenGroupManagerSpec: QuickSpec {
                             dependencies: dependencies
                         ) { didComplete = true }
                         
-                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                         expect(mockStorage)
                             .to(call(matchingParameters: true) {
                                 $0.setOpenGroup(
@@ -1441,12 +1441,12 @@ class OpenGroupManagerSpec: QuickSpec {
                         expect(testGroupThread.groupModel.groupImage)
                             .toEventuallyNot(
                                 beNil(),
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                         expect(testGroupThread.numSaveCalls)
                             .toEventually(
                                 equal(2),   // Call to save the open group and then to save the image
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                     }
                     
@@ -1492,7 +1492,7 @@ class OpenGroupManagerSpec: QuickSpec {
                             dependencies: dependencies
                         ) { didComplete = true }
                         
-                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                         expect(mockStorage)
                             .to(call(matchingParameters: true) {
                                 $0.setOpenGroup(
@@ -1512,12 +1512,12 @@ class OpenGroupManagerSpec: QuickSpec {
                         expect(testGroupThread.groupModel.groupImage)
                             .toEventuallyNot(
                                 beNil(),
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                         expect(testGroupThread.numSaveCalls)
                             .toEventually(
                                 equal(2),   // Call to save the open group and then to save the image
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                     }
                     
@@ -1598,7 +1598,7 @@ class OpenGroupManagerSpec: QuickSpec {
                             dependencies: dependencies
                         ) { didComplete = true }
                         
-                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                         expect(mockStorage)
                             .to(call(matchingParameters: true) {
                                 $0.setOpenGroup(
@@ -1618,12 +1618,12 @@ class OpenGroupManagerSpec: QuickSpec {
                         expect(testGroupThread.groupModel.groupImage)
                             .toEventuallyNot(
                                 beNil(),
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                         expect(testGroupThread.numSaveCalls)
                             .toEventually(
                                 equal(2),   // Call to save the open group and then to save the image
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                     }
                     
@@ -1639,16 +1639,16 @@ class OpenGroupManagerSpec: QuickSpec {
                             dependencies: dependencies
                         ) { didComplete = true }
                         
-                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                         expect(testGroupThread.groupModel.groupImage)
                             .toEventually(
                                 beNil(),
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                         expect(testGroupThread.numSaveCalls)
                             .toEventually(
                                 equal(1),
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                     }
                     
@@ -1710,16 +1710,16 @@ class OpenGroupManagerSpec: QuickSpec {
                             dependencies: dependencies
                         ) { didComplete = true }
                         
-                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                         expect(testGroupThread.groupModel.groupImage)
                             .toEventually(
                                 beNil(),
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                         expect(testGroupThread.numSaveCalls)
                             .toEventually(
                                 equal(1),
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                     }
                     
@@ -1778,16 +1778,16 @@ class OpenGroupManagerSpec: QuickSpec {
                             dependencies: dependencies
                         ) { didComplete = true }
                         
-                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(100))
+                        expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
                         expect(testGroupThread.groupModel.groupImage)
                             .toEventuallyNot(
                                 beNil(),
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                         expect(testGroupThread.numSaveCalls)
                             .toEventually(
                                 equal(2),   // Call to save the open group and then to save the image
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                     }
                 }
@@ -1958,7 +1958,7 @@ class OpenGroupManagerSpec: QuickSpec {
                     expect(testIncomingMessage.didCallSave)
                         .toEventually(
                             beTrue(),
-                            timeout: .milliseconds(100)
+                            timeout: .milliseconds(50)
                         )
                 }
                 
@@ -1989,7 +1989,7 @@ class OpenGroupManagerSpec: QuickSpec {
                     expect(testIncomingMessage.didCallSave)
                         .toEventually(
                             beTrue(),
-                            timeout: .milliseconds(100)
+                            timeout: .milliseconds(50)
                         )
                 }
                 
@@ -2022,7 +2022,7 @@ class OpenGroupManagerSpec: QuickSpec {
                         expect(testIncomingMessage.didCallRemove)
                             .toEventually(
                                 beTrue(),
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                     }
                     
@@ -2054,7 +2054,7 @@ class OpenGroupManagerSpec: QuickSpec {
                         expect(testIncomingMessage.didCallRemove)
                             .toEventuallyNot(
                                 beTrue(),
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                     }
                     
@@ -2087,7 +2087,7 @@ class OpenGroupManagerSpec: QuickSpec {
                         expect(testIncomingMessage.didCallRemove)
                             .toEventuallyNot(
                                 beTrue(),
-                                timeout: .milliseconds(100)
+                                timeout: .milliseconds(50)
                             )
                     }
                 }
@@ -3060,6 +3060,7 @@ class OpenGroupManagerSpec: QuickSpec {
                         .when { $0.setOpenGroupImage(to: any(), for: any(), on: any(), using: anyAny()) }
                         .thenReturn(())
                     mockUserDefaults.when { $0.object(forKey: any()) }.thenReturn(nil)
+                    mockUserDefaults.when { $0.set(anyAny(), forKey: any()) }.thenReturn(())
                 }
                 
                 it("caches the promise if there is no cached promise") {
@@ -3133,7 +3134,7 @@ class OpenGroupManagerSpec: QuickSpec {
                                     )
                                 ]
                             ),
-                            timeout: .milliseconds(100)
+                            timeout: .milliseconds(50)
                         )
                 }
                 
@@ -3157,7 +3158,7 @@ class OpenGroupManagerSpec: QuickSpec {
                     expect(error?.localizedDescription)
                         .toEventually(
                             equal(HTTP.Error.invalidResponse.localizedDescription),
-                            timeout: .milliseconds(100)
+                            timeout: .milliseconds(50)
                         )
                     expect(TestRoomsApi.callCounter).to(equal(9))   // First attempt + 8 retries
                 }
@@ -3177,7 +3178,7 @@ class OpenGroupManagerSpec: QuickSpec {
                     expect(error?.localizedDescription)
                         .toEventually(
                             equal(HTTP.Error.invalidResponse.localizedDescription),
-                            timeout: .milliseconds(100)
+                            timeout: .milliseconds(50)
                         )
                     expect(mockOGMCache)
                         .to(call(matchingParameters: true) {
@@ -3235,11 +3236,391 @@ class OpenGroupManagerSpec: QuickSpec {
                                     using: testTransaction! as Any
                                 )
                             },
-                            timeout: .milliseconds(100)
+                            timeout: .milliseconds(50)
                         )
                 }
             }
             
+            // MARK: - --roomImage
+            
+            context("when getting a room image") {
+                beforeEach {
+                    class TestImageApi: TestOnionRequestAPI {
+                        override class var mockResponse: Data? { return Data([1, 2, 3]) }
+                    }
+                    dependencies = dependencies.with(onionApi: TestImageApi.self)
+                    
+                    mockUserDefaults.when { $0.object(forKey: any()) }.thenReturn(nil)
+                    mockUserDefaults.when { $0.set(anyAny(), forKey: any()) }.thenReturn(())
+                    mockStorage.when { $0.getOpenGroupImage(for: any(), on: any()) }.thenReturn(nil)
+                    mockStorage
+                        .when { $0.setOpenGroupImage(to: any(), for: any(), on: any(), using: anyAny()) }
+                        .thenReturn(())
+                    mockOGMCache.when { $0.groupImagePromises }.thenReturn([:])
+                }
+                
+                it("retrieves the image retrieval promise from the cache if it exists") {
+                    let (promise, _) = Promise<Data>.pending()
+                    mockOGMCache
+                        .when { $0.groupImagePromises }
+                        .thenReturn(["testServer.testRoom": promise])
+                    
+                    expect(
+                        OpenGroupManager
+                            .roomImage(
+                                1,
+                                for: "testRoom",
+                                on: "testServer",
+                                using: dependencies
+                            )
+                    ).to(equal(promise))
+                }
+                
+                it("does not save the fetched image to storage") {
+                    let promise = OpenGroupManager
+                        .roomImage(
+                            1,
+                            for: "testRoom",
+                            on: "testServer",
+                            using: dependencies
+                        )
+                    promise.retainUntilComplete()
+                    
+                    expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50))
+                    expect(mockStorage)
+                        .toEventuallyNot(
+                            call(matchingParameters: true) {
+                                $0.setOpenGroupImage(
+                                    to: Data([1, 2, 3]),
+                                    for: "testRoom",
+                                    on: "testServer",
+                                    using: testTransaction! as Any
+                                )
+                            },
+                            timeout: .milliseconds(50)
+                        )
+                }
+                
+                it("does not update the image update timestamp") {
+                    let promise = OpenGroupManager
+                        .roomImage(
+                            1,
+                            for: "testRoom",
+                            on: "testServer",
+                            using: dependencies
+                        )
+                    promise.retainUntilComplete()
+                    
+                    expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50))
+                    expect(mockUserDefaults)
+                        .toEventuallyNot(
+                            call(matchingParameters: true) {
+                                $0.set(
+                                    dependencies.date,
+                                    forKey: SNUserDefaults.Date.lastOpenGroupImageUpdate.rawValue
+                                )
+                            },
+                            timeout: .milliseconds(50)
+                        )
+                }
+                
+                it("adds the image retrieval promise to the cache") {
+                    class TestNeverReturningApi: OnionRequestAPIType {
+                        static func sendOnionRequest(_ request: URLRequest, to server: String, using version: OnionRequestAPI.Version, with x25519PublicKey: String) -> Promise<(OnionRequestResponseInfoType, Data?)> {
+                            return Promise<(OnionRequestResponseInfoType, Data?)>.pending().promise
+                        }
+                        
+                        static func sendOnionRequest(to snode: Snode, invoking method: Snode.Method, with parameters: JSON, using version: OnionRequestAPI.Version, associatedWith publicKey: String?) -> Promise<Data> {
+                            return Promise.value(Data())
+                        }
+                    }
+                    dependencies = dependencies.with(onionApi: TestNeverReturningApi.self)
+                    
+                    let promise = OpenGroupManager.roomImage(
+                        1,
+                        for: "testRoom",
+                        on: "testServer",
+                        using: dependencies
+                    )
+                    
+                    expect(mockOGMCache)
+                        .toEventually(
+                            call(matchingParameters: true) {
+                                $0.groupImagePromises = ["testServer.testRoom": promise]
+                            },
+                            timeout: .milliseconds(50)
+                        )
+                }
+                
+                context("for the default server") {
+                    it("fetches a new image if there is no cached one") {
+                        var result: Data?
+                        
+                        let promise = OpenGroupManager
+                            .roomImage(
+                                1,
+                                for: "testRoom",
+                                on: OpenGroupAPI.defaultServer,
+                                using: dependencies
+                            )
+                            .done { result = $0 }
+                        promise.retainUntilComplete()
+                        
+                        expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50))
+                        expect(result).toEventually(equal(Data([1, 2, 3])), timeout: .milliseconds(50))
+                    }
+                    
+                    it("saves the fetched image to storage") {
+                        let promise = OpenGroupManager
+                            .roomImage(
+                                1,
+                                for: "testRoom",
+                                on: OpenGroupAPI.defaultServer,
+                                using: dependencies
+                            )
+                        promise.retainUntilComplete()
+                        
+                        expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50))
+                        expect(mockStorage)
+                            .toEventually(
+                                call(matchingParameters: true) {
+                                    $0.setOpenGroupImage(
+                                        to: Data([1, 2, 3]),
+                                        for: "testRoom",
+                                        on: OpenGroupAPI.defaultServer,
+                                        using: testTransaction! as Any
+                                    )
+                                },
+                                timeout: .milliseconds(50)
+                            )
+                    }
+                    
+                    it("updates the image update timestamp") {
+                        let promise = OpenGroupManager
+                            .roomImage(
+                                1,
+                                for: "testRoom",
+                                on: OpenGroupAPI.defaultServer,
+                                using: dependencies
+                            )
+                        promise.retainUntilComplete()
+                        
+                        expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50))
+                        expect(mockUserDefaults)
+                            .toEventually(
+                                call(matchingParameters: true) {
+                                    $0.set(
+                                        dependencies.date,
+                                        forKey: SNUserDefaults.Date.lastOpenGroupImageUpdate.rawValue
+                                    )
+                                },
+                                timeout: .milliseconds(50)
+                            )
+                    }
+                    
+                    context("and there is a cached image") {
+                        beforeEach {
+                            mockUserDefaults.when { $0.object(forKey: any()) }.thenReturn(dependencies.date)
+                            mockStorage
+                                .when { $0.getOpenGroupImage(for: any(), on: any()) }
+                                .thenReturn(Data([2, 3, 4]))
+                        }
+                        
+                        it("retrieves the cached image") {
+                            var result: Data?
+                            
+                            let promise = OpenGroupManager
+                                .roomImage(
+                                    1,
+                                    for: "testRoom",
+                                    on: OpenGroupAPI.defaultServer,
+                                    using: dependencies
+                                )
+                                .done { result = $0 }
+                            promise.retainUntilComplete()
+                            
+                            expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50))
+                            expect(result).toEventually(equal(Data([2, 3, 4])), timeout: .milliseconds(50))
+                        }
+                        
+                        it("fetches a new image if the cached on is older than a week") {
+                            mockUserDefaults
+                                .when { $0.object(forKey: any()) }
+                                .thenReturn(
+                                    Date(timeIntervalSince1970:
+                                        (dependencies.date.timeIntervalSince1970 - (7 * 24 * 60 * 60) - 1)
+                                    )
+                                )
+                            
+                            var result: Data?
+                            
+                            let promise = OpenGroupManager
+                                .roomImage(
+                                    1,
+                                    for: "testRoom",
+                                    on: OpenGroupAPI.defaultServer,
+                                    using: dependencies
+                                )
+                                .done { result = $0 }
+                            promise.retainUntilComplete()
+                            
+                            expect(promise.isFulfilled).toEventually(beTrue(), timeout: .milliseconds(50))
+                            expect(result).toEventually(equal(Data([1, 2, 3])), timeout: .milliseconds(50))
+                        }
+                    }
+                }
+            }
+            
+            // MARK: - --parseOpenGroup
+            
+            context("when parsing an open group url") {
+                it("handles the example urls correctly") {
+                    let validUrls: [String] = [
+                         "https://sessionopengroup.co/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
+                         "https://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
+                         "http://sessionopengroup.co/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
+                         "http://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
+                         "sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
+                         "sessionopengroup.co/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
+                         "https://143.198.213.225:443/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
+                         "https://143.198.213.225:443/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
+                         "143.198.213.255:80/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
+                         "143.198.213.255:80/r/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
+                    ]
+                    let processedValues: [(room: String, server: String, publicKey: String)] = validUrls
+                        .map { OpenGroupManager.parseOpenGroup(from: $0) }
+                        .compactMap { $0 }
+                    let processedRooms: [String] = processedValues.map { $0.room }
+                    let processedServers: [String] = processedValues.map { $0.server }
+                    let processedPublicKeys: [String] = processedValues.map { $0.publicKey }
+                    let expectedRooms: [String] = [String](repeating: "main", count: 10)
+                    let expectedServers: [String] = [
+                        "https://sessionopengroup.co",
+                        "https://sessionopengroup.co",
+                        "http://sessionopengroup.co",
+                        "http://sessionopengroup.co",
+                        "http://sessionopengroup.co",
+                        "http://sessionopengroup.co",
+                        "https://143.198.213.225:443",
+                        "https://143.198.213.225:443",
+                        "http://143.198.213.255:80",
+                        "http://143.198.213.255:80"
+                    ]
+                    let expectedPublicKeys: [String] = [String](
+                        repeating: "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c",
+                        count: 10
+                    )
+                    
+                    expect(processedValues.count).to(equal(validUrls.count))
+                    expect(processedRooms).to(equal(expectedRooms))
+                    expect(processedServers).to(equal(expectedServers))
+                    expect(processedPublicKeys).to(equal(expectedPublicKeys))
+                }
+                
+                it("handles the r prefix if present") {
+                    let info = OpenGroupManager.parseOpenGroup(
+                        from: [
+                            "https://sessionopengroup.co/r/main?",
+                            "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
+                        ].joined()
+                    )
+                    
+                    expect(info?.room).to(equal("main"))
+                    expect(info?.server).to(equal("https://sessionopengroup.co"))
+                    expect(info?.publicKey).to(equal("658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"))
+                }
+                
+                it("fails if there is no room") {
+                    let info = OpenGroupManager.parseOpenGroup(
+                        from: [
+                            "https://sessionopengroup.co?",
+                            "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
+                        ].joined()
+                    )
+                    
+                    expect(info?.room).to(beNil())
+                    expect(info?.server).to(beNil())
+                    expect(info?.publicKey).to(beNil())
+                }
+                
+                it("fails if there is no public key parameter") {
+                    let info = OpenGroupManager.parseOpenGroup(
+                        from: "https://sessionopengroup.co/r/main"
+                    )
+                    
+                    expect(info?.room).to(beNil())
+                    expect(info?.server).to(beNil())
+                    expect(info?.publicKey).to(beNil())
+                }
+                
+                it("fails if the public key parameter is not 64 characters") {
+                    let info = OpenGroupManager.parseOpenGroup(
+                        from: [
+                            "https://sessionopengroup.co/r/main?",
+                            "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231"
+                        ].joined()
+                    )
+                    
+                    expect(info?.room).to(beNil())
+                    expect(info?.server).to(beNil())
+                    expect(info?.publicKey).to(beNil())
+                }
+                
+                it("fails if the public key parameter is not a hex string") {
+                    let info = OpenGroupManager.parseOpenGroup(
+                        from: [
+                            "https://sessionopengroup.co/r/main?",
+                            "public_key=!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
+                        ].joined()
+                    )
+                    
+                    expect(info?.room).to(beNil())
+                    expect(info?.server).to(beNil())
+                    expect(info?.publicKey).to(beNil())
+                }
+                
+                it("maintains the same TLS") {
+                    let server1 = OpenGroupManager.parseOpenGroup(
+                        from: [
+                            "sessionopengroup.co/r/main?",
+                            "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
+                        ].joined()
+                    )?.server
+                    let server2 = OpenGroupManager.parseOpenGroup(
+                        from: [
+                            "http://sessionopengroup.co/r/main?",
+                            "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
+                        ].joined()
+                    )?.server
+                    let server3 = OpenGroupManager.parseOpenGroup(
+                        from: [
+                            "https://sessionopengroup.co/r/main?",
+                            "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
+                        ].joined()
+                    )?.server
+                    
+                    expect(server1).to(equal("http://sessionopengroup.co"))
+                    expect(server2).to(equal("http://sessionopengroup.co"))
+                    expect(server3).to(equal("https://sessionopengroup.co"))
+                }
+                
+                it("maintains the same port") {
+                    let server1 = OpenGroupManager.parseOpenGroup(
+                        from: [
+                            "https://sessionopengroup.co/r/main?",
+                            "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
+                        ].joined()
+                    )?.server
+                    let server2 = OpenGroupManager.parseOpenGroup(
+                        from: [
+                            "https://sessionopengroup.co:1234/r/main?",
+                            "public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c"
+                        ].joined()
+                    )?.server
+                    
+                    expect(server1).to(equal("https://sessionopengroup.co"))
+                    expect(server2).to(equal("https://sessionopengroup.co:1234"))
+                }
             }
         }
     }