Make the SessionDirectories type responsible for cleaning up data. (#3261)

This commit is contained in:
Doug 2024-09-11 14:32:03 +01:00 committed by GitHub
parent 84a3ffc135
commit af3a6ccbed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 192 additions and 80 deletions

View File

@ -57,4 +57,8 @@ extension FileManager {
return size return size
} }
func numberOfItems(at url: URL) throws -> Int {
try contentsOfDirectory(at: url, includingPropertiesForKeys: nil).count
}
} }

View File

@ -140,13 +140,7 @@ class AuthenticationService: AuthenticationServiceProtocol {
} }
private func rotateSessionDirectory() { private func rotateSessionDirectory() {
if FileManager.default.directoryExists(at: sessionDirectories.dataDirectory) { sessionDirectories.delete()
try? FileManager.default.removeItem(at: sessionDirectories.dataDirectory)
}
if FileManager.default.directoryExists(at: sessionDirectories.cacheDirectory) {
try? FileManager.default.removeItem(at: sessionDirectories.cacheDirectory)
}
sessionDirectories = .init() sessionDirectories = .init()
} }

View File

@ -110,8 +110,7 @@ class KeychainController: KeychainControllerProtocol {
fatalError("Something has gone mega wrong, all bets are off.") fatalError("Something has gone mega wrong, all bets are off.")
} }
let restorationToken = RestorationToken(session: session, let restorationToken = RestorationToken(session: session,
sessionDirectory: oldToken.sessionDirectory, sessionDirectories: oldToken.sessionDirectories,
cacheDirectory: oldToken.cacheDirectory,
passphrase: oldToken.passphrase, passphrase: oldToken.passphrase,
pusherNotificationClientIdentifier: oldToken.pusherNotificationClientIdentifier) pusherNotificationClientIdentifier: oldToken.pusherNotificationClientIdentifier)
setRestorationToken(restorationToken, forUsername: session.userId) setRestorationToken(restorationToken, forUsername: session.userId)

View File

@ -77,13 +77,7 @@ final class QRCodeLoginService: QRCodeLoginServiceProtocol {
} }
private func rotateSessionDirectory() { private func rotateSessionDirectory() {
if FileManager.default.directoryExists(at: sessionDirectories.dataDirectory) { sessionDirectories.delete()
try? FileManager.default.removeItem(at: sessionDirectories.dataDirectory)
}
if FileManager.default.directoryExists(at: sessionDirectories.cacheDirectory) {
try? FileManager.default.removeItem(at: sessionDirectories.cacheDirectory)
}
sessionDirectories = .init() sessionDirectories = .init()
} }

View File

@ -10,10 +10,17 @@ import MatrixRustSDK
struct RestorationToken: Equatable { struct RestorationToken: Equatable {
let session: MatrixRustSDK.Session let session: MatrixRustSDK.Session
let sessionDirectory: URL let sessionDirectories: SessionDirectories
let cacheDirectory: URL
let passphrase: String? let passphrase: String?
let pusherNotificationClientIdentifier: String? let pusherNotificationClientIdentifier: String?
enum CodingKeys: CodingKey {
case session
case sessionDirectory
case cacheDirectory
case passphrase
case pusherNotificationClientIdentifier
}
} }
extension RestorationToken: Codable { extension RestorationToken: Codable {
@ -35,11 +42,19 @@ extension RestorationToken: Codable {
} }
self = try .init(session: session, self = try .init(session: session,
sessionDirectory: sessionDirectories.dataDirectory, sessionDirectories: sessionDirectories,
cacheDirectory: sessionDirectories.cacheDirectory,
passphrase: container.decodeIfPresent(String.self, forKey: .passphrase), passphrase: container.decodeIfPresent(String.self, forKey: .passphrase),
pusherNotificationClientIdentifier: container.decodeIfPresent(String.self, forKey: .pusherNotificationClientIdentifier)) pusherNotificationClientIdentifier: container.decodeIfPresent(String.self, forKey: .pusherNotificationClientIdentifier))
} }
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(session, forKey: .session)
try container.encode(sessionDirectories.dataDirectory, forKey: .sessionDirectory)
try container.encode(sessionDirectories.cacheDirectory, forKey: .cacheDirectory)
try container.encode(passphrase, forKey: .passphrase)
try container.encode(pusherNotificationClientIdentifier, forKey: .pusherNotificationClientIdentifier)
}
} }
extension MatrixRustSDK.Session: Codable { extension MatrixRustSDK.Session: Codable {

View File

@ -13,6 +13,50 @@ struct SessionDirectories: Hashable, Codable {
var dataPath: String { dataDirectory.path(percentEncoded: false) } var dataPath: String { dataDirectory.path(percentEncoded: false) }
var cachePath: String { cacheDirectory.path(percentEncoded: false) } var cachePath: String { cacheDirectory.path(percentEncoded: false) }
// MARK: Data Management
/// Removes the directories from disk if they have been created.
func delete() {
do {
if FileManager.default.directoryExists(at: dataDirectory) {
try FileManager.default.removeItem(at: dataDirectory)
}
} catch {
MXLog.failure("Failed deleting the session data: \(error)")
}
do {
if FileManager.default.directoryExists(at: cacheDirectory) {
try FileManager.default.removeItem(at: cacheDirectory)
}
} catch {
MXLog.failure("Failed deleting the session caches: \(error)")
}
}
/// Deletes the Rust state store and event cache data, leaving the crypto store and both
/// session directories in place along with any other data that may have been written in them.
func deleteTransientUserData() {
do {
let prefix = "matrix-sdk-state"
try deleteFiles(at: dataDirectory, with: prefix)
} catch {
MXLog.failure("Failed clearing state store: \(error)")
}
do {
let prefix = "matrix-sdk-event-cache"
try deleteFiles(at: cacheDirectory, with: prefix)
} catch {
MXLog.failure("Failed clearing event cache store: \(error)")
}
}
private func deleteFiles(at url: URL, with prefix: String) throws {
let sessionDirectoryContents = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
for url in sessionDirectoryContents where url.lastPathComponent.hasPrefix(prefix) {
try FileManager.default.removeItem(at: url)
}
}
} }
extension SessionDirectories { extension SessionDirectories {

View File

@ -14,7 +14,6 @@ class UserSessionStore: UserSessionStoreProtocol {
private let appSettings: AppSettings private let appSettings: AppSettings
private let networkMonitor: NetworkMonitorProtocol private let networkMonitor: NetworkMonitorProtocol
private let appHooks: AppHooks private let appHooks: AppHooks
private let matrixSDKStateKey = "matrix-sdk-state"
/// Whether or not there are sessions in the store. /// Whether or not there are sessions in the store.
var hasSessions: Bool { !keychainController.restorationTokens().isEmpty } var hasSessions: Bool { !keychainController.restorationTokens().isEmpty }
@ -55,7 +54,7 @@ class UserSessionStore: UserSessionStoreProtocol {
// On any restoration failure reset the token and restart // On any restoration failure reset the token and restart
keychainController.removeRestorationTokenForUsername(credentials.userID) keychainController.removeRestorationTokenForUsername(credentials.userID)
deleteSessionDirectories(for: credentials) credentials.restorationToken.sessionDirectories.delete()
return .failure(error) return .failure(error)
} }
@ -68,8 +67,7 @@ class UserSessionStore: UserSessionStoreProtocol {
let clientProxy = await setupProxyForClient(client) let clientProxy = await setupProxyForClient(client)
keychainController.setRestorationToken(RestorationToken(session: session, keychainController.setRestorationToken(RestorationToken(session: session,
sessionDirectory: sessionDirectories.dataDirectory, sessionDirectories: sessionDirectories,
cacheDirectory: sessionDirectories.cacheDirectory,
passphrase: passphrase, passphrase: passphrase,
pusherNotificationClientIdentifier: clientProxy.pusherNotificationClientIdentifier), pusherNotificationClientIdentifier: clientProxy.pusherNotificationClientIdentifier),
forUsername: userID) forUsername: userID)
@ -87,7 +85,7 @@ class UserSessionStore: UserSessionStoreProtocol {
keychainController.removeRestorationTokenForUsername(userID) keychainController.removeRestorationTokenForUsername(userID)
if let credentials { if let credentials {
deleteSessionDirectories(for: credentials) credentials.restorationToken.sessionDirectories.delete()
} }
} }
@ -96,7 +94,7 @@ class UserSessionStore: UserSessionStoreProtocol {
MXLog.error("Failed to clearing caches: Credentials missing") MXLog.error("Failed to clearing caches: Credentials missing")
return return
} }
deleteCaches(for: credentials) credentials.restorationToken.sessionDirectories.deleteTransientUserData()
} }
// MARK: - Private // MARK: - Private
@ -125,8 +123,8 @@ class UserSessionStore: UserSessionStoreProtocol {
slidingSync: .restored, slidingSync: .restored,
sessionDelegate: keychainController, sessionDelegate: keychainController,
appHooks: appHooks) appHooks: appHooks)
.sessionPaths(dataPath: credentials.restorationToken.sessionDirectory.path(percentEncoded: false), .sessionPaths(dataPath: credentials.restorationToken.sessionDirectories.dataPath,
cachePath: credentials.restorationToken.cacheDirectory.path(percentEncoded: false)) cachePath: credentials.restorationToken.sessionDirectories.cachePath)
.username(username: credentials.userID) .username(username: credentials.userID)
.homeserverUrl(url: homeserverURL) .homeserverUrl(url: homeserverURL)
.passphrase(passphrase: credentials.restorationToken.passphrase) .passphrase(passphrase: credentials.restorationToken.passphrase)
@ -148,37 +146,4 @@ class UserSessionStore: UserSessionStoreProtocol {
networkMonitor: networkMonitor, networkMonitor: networkMonitor,
appSettings: appSettings) appSettings: appSettings)
} }
private func deleteSessionDirectories(for credentials: KeychainCredentials) {
do {
try FileManager.default.removeItem(at: credentials.restorationToken.sessionDirectory)
} catch {
MXLog.failure("Failed deleting the session data: \(error)")
}
do {
try FileManager.default.removeItem(at: credentials.restorationToken.cacheDirectory)
} catch {
MXLog.failure("Failed deleting the session caches: \(error)")
}
}
private func deleteCaches(for credentials: KeychainCredentials) {
do {
try deleteContentsOfDirectory(at: credentials.restorationToken.sessionDirectory)
} catch {
MXLog.failure("Failed clearing state store: \(error)")
}
do {
try deleteContentsOfDirectory(at: credentials.restorationToken.cacheDirectory)
} catch {
MXLog.failure("Failed clearing event cache store: \(error)")
}
}
private func deleteContentsOfDirectory(at url: URL) throws {
let sessionDirectoryContents = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
for url in sessionDirectoryContents where url.path.contains(matrixSDKStateKey) {
try FileManager.default.removeItem(at: url)
}
}
} }

View File

@ -30,8 +30,8 @@ final class NSEUserSession {
slidingSync: .restored, slidingSync: .restored,
sessionDelegate: clientSessionDelegate, sessionDelegate: clientSessionDelegate,
appHooks: appHooks) appHooks: appHooks)
.sessionPaths(dataPath: credentials.restorationToken.sessionDirectory.path(percentEncoded: false), .sessionPaths(dataPath: credentials.restorationToken.sessionDirectories.dataPath,
cachePath: credentials.restorationToken.cacheDirectory.path(percentEncoded: false)) cachePath: credentials.restorationToken.sessionDirectories.cachePath)
.username(username: credentials.userID) .username(username: credentials.userID)
.homeserverUrl(url: homeserverURL) .homeserverUrl(url: homeserverURL)
.passphrase(passphrase: credentials.restorationToken.passphrase) .passphrase(passphrase: credentials.restorationToken.passphrase)

View File

@ -31,8 +31,7 @@ class KeychainControllerTests: XCTestCase {
homeserverUrl: "homeserverUrl", homeserverUrl: "homeserverUrl",
oidcData: "oidcData", oidcData: "oidcData",
slidingSyncVersion: .proxy(url: "https://my.sync.proxy")), slidingSyncVersion: .proxy(url: "https://my.sync.proxy")),
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString), sessionDirectories: .init(),
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
passphrase: "passphrase", passphrase: "passphrase",
pusherNotificationClientIdentifier: "pusherClientID") pusherNotificationClientIdentifier: "pusherClientID")
keychain.setRestorationToken(restorationToken, forUsername: username) keychain.setRestorationToken(restorationToken, forUsername: username)
@ -51,8 +50,7 @@ class KeychainControllerTests: XCTestCase {
homeserverUrl: "homeserverUrl", homeserverUrl: "homeserverUrl",
oidcData: "oidcData", oidcData: "oidcData",
slidingSyncVersion: .proxy(url: "https://my.sync.proxy")), slidingSyncVersion: .proxy(url: "https://my.sync.proxy")),
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString), sessionDirectories: .init(),
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
passphrase: "passphrase", passphrase: "passphrase",
pusherNotificationClientIdentifier: "pusherClientID") pusherNotificationClientIdentifier: "pusherClientID")
keychain.setRestorationToken(restorationToken, forUsername: username) keychain.setRestorationToken(restorationToken, forUsername: username)
@ -77,8 +75,7 @@ class KeychainControllerTests: XCTestCase {
homeserverUrl: "homeserverUrl", homeserverUrl: "homeserverUrl",
oidcData: "oidcData", oidcData: "oidcData",
slidingSyncVersion: .proxy(url: "https://my.sync.proxy")), slidingSyncVersion: .proxy(url: "https://my.sync.proxy")),
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString), sessionDirectories: .init(),
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
passphrase: "passphrase", passphrase: "passphrase",
pusherNotificationClientIdentifier: "pusherClientID") pusherNotificationClientIdentifier: "pusherClientID")
keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com") keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com")
@ -102,8 +99,7 @@ class KeychainControllerTests: XCTestCase {
homeserverUrl: "homeserverUrl", homeserverUrl: "homeserverUrl",
oidcData: "oidcData", oidcData: "oidcData",
slidingSyncVersion: .proxy(url: "https://my.sync.proxy")), slidingSyncVersion: .proxy(url: "https://my.sync.proxy")),
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString), sessionDirectories: .init(),
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
passphrase: "passphrase", passphrase: "passphrase",
pusherNotificationClientIdentifier: "pusherClientID") pusherNotificationClientIdentifier: "pusherClientID")
keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com") keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com")
@ -135,8 +131,7 @@ class KeychainControllerTests: XCTestCase {
homeserverUrl: "homeserverUrl", homeserverUrl: "homeserverUrl",
oidcData: "oidcData", oidcData: "oidcData",
slidingSyncVersion: .native), slidingSyncVersion: .native),
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString), sessionDirectories: .init(),
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
passphrase: "passphrase", passphrase: "passphrase",
pusherNotificationClientIdentifier: "pusherClientID") pusherNotificationClientIdentifier: "pusherClientID")
keychain.setRestorationToken(restorationToken, forUsername: username) keychain.setRestorationToken(restorationToken, forUsername: username)

View File

@ -29,9 +29,9 @@ class RestorationTokenTests: XCTestCase {
XCTAssertEqual(decodedToken.session, originalToken.session, "The session should not be changed.") XCTAssertEqual(decodedToken.session, originalToken.session, "The session should not be changed.")
XCTAssertNil(decodedToken.passphrase, "There should not be a passphrase.") XCTAssertNil(decodedToken.passphrase, "There should not be a passphrase.")
XCTAssertNil(decodedToken.pusherNotificationClientIdentifier, "There should not be a push notification client ID.") XCTAssertNil(decodedToken.pusherNotificationClientIdentifier, "There should not be a push notification client ID.")
XCTAssertEqual(decodedToken.sessionDirectory, .sessionsBaseDirectory.appending(component: "@user_example.com"), XCTAssertEqual(decodedToken.sessionDirectories.dataDirectory, .sessionsBaseDirectory.appending(component: "@user_example.com"),
"The session directory should match the original location set by the Rust SDK from our base directory.") "The session directory should match the original location set by the Rust SDK from our base directory.")
XCTAssertEqual(decodedToken.cacheDirectory, .cachesBaseDirectory.appending(component: "@user_example.com"), XCTAssertEqual(decodedToken.sessionDirectories.cacheDirectory, .cachesBaseDirectory.appending(component: "@user_example.com"),
"The cache directory should be derived from the session directory but in the caches directory.") "The cache directory should be derived from the session directory but in the caches directory.")
} }
@ -58,15 +58,16 @@ class RestorationTokenTests: XCTestCase {
XCTAssertEqual(decodedToken.passphrase, originalToken.passphrase, "The passphrase should not be changed.") XCTAssertEqual(decodedToken.passphrase, originalToken.passphrase, "The passphrase should not be changed.")
XCTAssertEqual(decodedToken.pusherNotificationClientIdentifier, originalToken.pusherNotificationClientIdentifier, XCTAssertEqual(decodedToken.pusherNotificationClientIdentifier, originalToken.pusherNotificationClientIdentifier,
"The push notification client identifier should not be changed.") "The push notification client identifier should not be changed.")
XCTAssertEqual(decodedToken.sessionDirectory, originalToken.sessionDirectory, "The session directory should not be changed.") XCTAssertEqual(decodedToken.sessionDirectories.dataDirectory, originalToken.sessionDirectory,
XCTAssertEqual(decodedToken.cacheDirectory, .cachesBaseDirectory.appending(component: sessionDirectoryName), "The session directory should not be changed.")
XCTAssertEqual(decodedToken.sessionDirectories.cacheDirectory, .cachesBaseDirectory.appending(component: sessionDirectoryName),
"The cache directory should be derived from the session directory but in the caches directory.") "The cache directory should be derived from the session directory but in the caches directory.")
} }
func testDecodeFromCurrentToken() throws { func testDecodeFromTokenV5() throws {
// Given an encoded restoration token in the current format. // Given an encoded restoration token in the 5th format that contains separate directories for session data and caches.
let sessionDirectoryName = UUID().uuidString let sessionDirectoryName = UUID().uuidString
let originalToken = RestorationToken(session: Session(accessToken: "1234", let originalToken = RestorationTokenV5(session: Session(accessToken: "1234",
refreshToken: "5678", refreshToken: "5678",
userId: "@user:example.com", userId: "@user:example.com",
deviceId: "D3V1C3", deviceId: "D3V1C3",
@ -82,6 +83,34 @@ class RestorationTokenTests: XCTestCase {
// When decoding the data. // When decoding the data.
let decodedToken = try JSONDecoder().decode(RestorationToken.self, from: data) let decodedToken = try JSONDecoder().decode(RestorationToken.self, from: data)
// Then the output should be a valid token.
XCTAssertEqual(decodedToken.session, originalToken.session, "The session should not be changed.")
XCTAssertEqual(decodedToken.passphrase, originalToken.passphrase, "The passphrase should not be changed.")
XCTAssertEqual(decodedToken.pusherNotificationClientIdentifier, originalToken.pusherNotificationClientIdentifier,
"The push notification client identifier should not be changed.")
XCTAssertEqual(decodedToken.sessionDirectories.dataDirectory, originalToken.sessionDirectory,
"The session directory should not be changed.")
XCTAssertEqual(decodedToken.sessionDirectories.cacheDirectory, originalToken.cacheDirectory,
"The cache directory should not be changed.")
}
func testDecodeFromCurrentToken() throws {
// Given an encoded restoration token in the current format.
let originalToken = RestorationToken(session: Session(accessToken: "1234",
refreshToken: "5678",
userId: "@user:example.com",
deviceId: "D3V1C3",
homeserverUrl: "https://matrix.example.com",
oidcData: "data-from-mas",
slidingSyncVersion: .native),
sessionDirectories: .init(),
passphrase: "passphrase",
pusherNotificationClientIdentifier: "pusher-identifier")
let data = try JSONEncoder().encode(originalToken)
// When decoding the data.
let decodedToken = try JSONDecoder().decode(RestorationToken.self, from: data)
// Then the output should be a valid token. // Then the output should be a valid token.
XCTAssertEqual(decodedToken, originalToken, "The token should remain identical.") XCTAssertEqual(decodedToken, originalToken, "The token should remain identical.")
} }
@ -97,3 +126,11 @@ struct RestorationTokenV4: Equatable, Codable {
let passphrase: String? let passphrase: String?
let pusherNotificationClientIdentifier: String? let pusherNotificationClientIdentifier: String?
} }
struct RestorationTokenV5: Equatable, Codable {
let session: MatrixRustSDK.Session
let sessionDirectory: URL
let cacheDirectory: URL
let passphrase: String?
let pusherNotificationClientIdentifier: String?
}

View File

@ -10,6 +10,8 @@ import XCTest
@testable import ElementX @testable import ElementX
class SessionDirectoriesTests: XCTestCase { class SessionDirectoriesTests: XCTestCase {
let fileManager = FileManager.default
func testInitWithUserID() { func testInitWithUserID() {
// Given only a user ID. // Given only a user ID.
let userID = "@user:matrix.org" let userID = "@user:matrix.org"
@ -51,4 +53,67 @@ class SessionDirectoriesTests: XCTestCase {
XCTAssertEqual(returnedDataPath, originalDataPath) XCTAssertEqual(returnedDataPath, originalDataPath)
XCTAssertEqual(returnedCachePath, originalCachePath) XCTAssertEqual(returnedCachePath, originalCachePath)
} }
func testDeleteDirectories() throws {
// Given a new set of session directories.
let sessionDirectories = SessionDirectories()
try fileManager.createDirectory(at: sessionDirectories.dataDirectory, withIntermediateDirectories: true)
try fileManager.createDirectory(at: sessionDirectories.cacheDirectory, withIntermediateDirectories: true)
XCTAssertTrue(fileManager.directoryExists(at: sessionDirectories.dataDirectory))
XCTAssertTrue(fileManager.directoryExists(at: sessionDirectories.cacheDirectory))
// When deleting the directories.
sessionDirectories.delete()
// Then neither directory should exist on disk.
XCTAssertFalse(fileManager.directoryExists(at: sessionDirectories.dataDirectory))
XCTAssertFalse(fileManager.directoryExists(at: sessionDirectories.cacheDirectory))
}
func testDeleteTransientUserData() throws {
// Given a set of session directories with some databases.
let sessionDirectories = SessionDirectories()
try fileManager.createDirectory(at: sessionDirectories.dataDirectory, withIntermediateDirectories: true)
try fileManager.createDirectory(at: sessionDirectories.cacheDirectory, withIntermediateDirectories: true)
XCTAssertTrue(fileManager.directoryExists(at: sessionDirectories.dataDirectory))
XCTAssertTrue(fileManager.directoryExists(at: sessionDirectories.cacheDirectory))
sessionDirectories.generateMockData()
XCTAssertTrue(fileManager.fileExists(atPath: sessionDirectories.mockStateStorePath))
XCTAssertTrue(fileManager.fileExists(atPath: sessionDirectories.mockCryptoStorePath))
XCTAssertTrue(fileManager.fileExists(atPath: sessionDirectories.mockEventCachePath))
XCTAssertEqual(try fileManager.numberOfItems(at: sessionDirectories.dataDirectory), 6)
XCTAssertEqual(try fileManager.numberOfItems(at: sessionDirectories.cacheDirectory), 3)
// When deleting transient user data.
sessionDirectories.deleteTransientUserData()
// Then the data directory should only contain the crypto store and the cache directory should remain but be empty.
XCTAssertTrue(fileManager.directoryExists(at: sessionDirectories.dataDirectory))
XCTAssertEqual(try fileManager.numberOfItems(at: sessionDirectories.dataDirectory), 3)
XCTAssertFalse(fileManager.fileExists(atPath: sessionDirectories.mockStateStorePath))
XCTAssertTrue(fileManager.fileExists(atPath: sessionDirectories.mockCryptoStorePath))
XCTAssertTrue(fileManager.directoryExists(at: sessionDirectories.cacheDirectory))
XCTAssertEqual(try fileManager.numberOfItems(at: sessionDirectories.cacheDirectory), 0)
XCTAssertFalse(fileManager.fileExists(atPath: sessionDirectories.mockEventCachePath))
}
}
private extension SessionDirectories {
var mockStateStorePath: String { dataDirectory.appending(component: "matrix-sdk-state.sqlite3").path(percentEncoded: false) }
var mockCryptoStorePath: String { dataDirectory.appending(component: "matrix-sdk-crypto.sqlite3").path(percentEncoded: false) }
var mockEventCachePath: String { cacheDirectory.appending(component: "matrix-sdk-event-cache.sqlite3").path(percentEncoded: false) }
func generateMockData() {
generateMockDatabase(atPath: mockStateStorePath)
generateMockDatabase(atPath: mockCryptoStorePath)
generateMockDatabase(atPath: mockEventCachePath)
}
private func generateMockDatabase(atPath path: String) {
FileManager.default.createFile(atPath: path, contents: nil)
FileManager.default.createFile(atPath: path + "-shm", contents: nil)
FileManager.default.createFile(atPath: path + "-wal", contents: nil)
}
} }