@@ -28,7 +28,8 @@ final class RemoteCredentialStoreTests: XCTestCase {
2828
2929 let files = try FileManager . default. contentsOfDirectory ( at: directory, includingPropertiesForKeys: nil )
3030 XCTAssertEqual ( files. count, 1 )
31- XCTAssertEqual ( files [ 0 ] . lastPathComponent, " server_one_with_unsafe_chars.json " )
31+ XCTAssertTrue ( files [ 0 ] . lastPathComponent. hasPrefix ( " server_one_with_unsafe_chars- " ) )
32+ XCTAssertTrue ( files [ 0 ] . lastPathComponent. hasSuffix ( " .json " ) )
3233 let attributes = try FileManager . default. attributesOfItem ( atPath: files [ 0 ] . path)
3334 XCTAssertEqual ( ( attributes [ . posixPermissions] as? NSNumber ) ? . intValue, 0o600 )
3435
@@ -79,6 +80,64 @@ final class RemoteCredentialStoreTests: XCTestCase {
7980 XCTAssertEqual ( files. count, count)
8081 }
8182
83+ func testSourceIDsWithSameLegacySanitizedNameUseDistinctCredentialFiles( ) async throws {
84+ let directory = try makeTemporaryDirectory ( )
85+ let store = RemoteCredentialStore ( directory: directory)
86+ let colonCredential = RemoteSourceCredential (
87+ kind: " emby " ,
88+ serverURL: " https://colon.example.test " ,
89+ username: nil ,
90+ password: nil ,
91+ accessToken: " colon-token " ,
92+ userID: nil
93+ )
94+ let slashCredential = RemoteSourceCredential (
95+ kind: " emby " ,
96+ serverURL: " https://slash.example.test " ,
97+ username: nil ,
98+ password: nil ,
99+ accessToken: " slash-token " ,
100+ userID: nil
101+ )
102+
103+ try await store. saveAsync ( colonCredential, sourceID: " server:a " )
104+ try await store. saveAsync ( slashCredential, sourceID: " server/a " )
105+
106+ let files = try FileManager . default. contentsOfDirectory ( atPath: directory. path)
107+ XCTAssertEqual ( files. count, 2 )
108+ XCTAssertEqual ( Set ( files) . count, 2 )
109+ XCTAssertFalse ( files. contains ( " server_a.json " ) )
110+ let loadedColonCredential = try await store. loadAsync ( sourceID: " server:a " )
111+ let loadedSlashCredential = try await store. loadAsync ( sourceID: " server/a " )
112+ XCTAssertEqual ( loadedColonCredential? . accessToken, " colon-token " )
113+ XCTAssertEqual ( loadedSlashCredential? . accessToken, " slash-token " )
114+ }
115+
116+ func testLoadAndDeleteRemainCompatibleWithLegacySanitizedFilename( ) async throws {
117+ let directory = try makeTemporaryDirectory ( )
118+ let store = RemoteCredentialStore ( directory: directory)
119+ let sourceID = " legacy:source/path "
120+ let legacyURL = directory. appendingPathComponent ( " legacy_source_path.json " )
121+ let legacyCredential = RemoteSourceCredential (
122+ kind: " plex " ,
123+ serverURL: " https://legacy.example.test " ,
124+ username: " legacy-user " ,
125+ password: " legacy-password " ,
126+ accessToken: " legacy-token " ,
127+ userID: " legacy-user-id "
128+ )
129+ try JSONEncoder ( ) . encode ( legacyCredential) . write ( to: legacyURL, options: . atomic)
130+
131+ let loaded = try await store. loadAsync ( sourceID: sourceID)
132+
133+ XCTAssertEqual ( loaded? . serverURL, " https://legacy.example.test " )
134+ XCTAssertEqual ( loaded? . accessToken, " legacy-token " )
135+
136+ await store. deleteAsync ( sourceID: sourceID)
137+
138+ XCTAssertFalse ( FileManager . default. fileExists ( atPath: legacyURL. path) )
139+ }
140+
82141 func testAsyncLoadReturnsNilForCorruptedJSONAndDeleteIsIdempotent( ) async throws {
83142 let directory = try makeTemporaryDirectory ( )
84143 let store = RemoteCredentialStore ( directory: directory)
@@ -94,7 +153,10 @@ final class RemoteCredentialStoreTests: XCTestCase {
94153 ) ,
95154 sourceID: sourceID
96155 )
97- let fileURL = directory. appendingPathComponent ( " corrupted.json " )
156+ let fileURL = try XCTUnwrap ( FileManager . default. contentsOfDirectory (
157+ at: directory,
158+ includingPropertiesForKeys: nil
159+ ) . first)
98160 try Data ( " { \" serverURL \" : " . utf8) . write ( to: fileURL)
99161
100162 let corruptedCredential = try await store. loadAsync ( sourceID: sourceID)
0 commit comments