@@ -6,7 +6,10 @@ import os from 'os';
66import { UUID } from 'bson' ;
77import { sortBy } from 'lodash' ;
88
9- import { ConnectionStorage } from './connection-storage' ;
9+ import {
10+ ConnectionStorage ,
11+ type ConnectionWithLegacyProps ,
12+ } from './connection-storage' ;
1013import type { ConnectionInfo } from './connection-info' ;
1114import Sinon from 'sinon' ;
1215
@@ -33,14 +36,23 @@ function getConnectionInfo(props: Partial<ConnectionInfo> = {}) {
3336
3437async function writeFakeConnection (
3538 tmpDir : string ,
36- connection : { connectionInfo : ConnectionInfo }
39+ connection : ConnectionWithLegacyProps
3740) {
38- const filePath = getConnectionFilePath ( tmpDir , connection . connectionInfo . id ) ;
41+ const _id = connection . _id || connection . connectionInfo ?. id ;
42+ if ( ! _id ) {
43+ throw new Error ( 'Connection should have _id or connectionInfo.id' ) ;
44+ }
45+ const filePath = getConnectionFilePath ( tmpDir , _id ) ;
3946 const connectionsDir = path . dirname ( filePath ) ;
4047 await fs . mkdir ( connectionsDir , { recursive : true } ) ;
4148 await fs . writeFile ( filePath , JSON . stringify ( connection ) ) ;
4249}
4350
51+ async function readConnection ( tmpDir : string , id : string ) {
52+ const content = await fs . readFile ( getConnectionFilePath ( tmpDir , id ) , 'utf-8' ) ;
53+ return JSON . parse ( content ) ;
54+ }
55+
4456const maxAllowedConnections = 10 ;
4557
4658describe ( 'ConnectionStorage' , function ( ) {
@@ -75,6 +87,238 @@ describe('ConnectionStorage', function () {
7587 }
7688 } ) ;
7789
90+ describe ( 'migrateToSafeStorage' , function ( ) {
91+ let sandbox : Sinon . SinonSandbox ;
92+ beforeEach ( function ( ) {
93+ sandbox = Sinon . createSandbox ( ) ;
94+ } ) ;
95+
96+ afterEach ( function ( ) {
97+ sandbox . restore ( ) ;
98+ } ) ;
99+
100+ context ( 'does not migrate connections' , function ( ) {
101+ it ( 'when there are no connections' , async function ( ) {
102+ await ConnectionStorage . migrateToSafeStorage ( ) ;
103+ const connections = await ConnectionStorage . loadAll ( ) ;
104+ expect ( connections ) . to . deep . equal ( [ ] ) ;
105+ } ) ;
106+
107+ it ( 'when there are only legacy connections' , async function ( ) {
108+ await writeFakeConnection ( tmpDir , connection1270 ) ;
109+
110+ const encryptSecretsSpy = sandbox . spy (
111+ ConnectionStorage ,
112+ 'encryptSecrets' as any
113+ ) ;
114+ const getKeytarCredentialsSpy = sandbox . spy (
115+ ConnectionStorage ,
116+ 'getKeytarCredentials' as any
117+ ) ;
118+
119+ await ConnectionStorage . migrateToSafeStorage ( ) ;
120+
121+ expect (
122+ encryptSecretsSpy . called ,
123+ 'it does not try to encrypt'
124+ ) . to . be . false ;
125+ expect (
126+ getKeytarCredentialsSpy . called ,
127+ 'it does not try to get secrets from keychain'
128+ ) . to . be . false ;
129+ } ) ;
130+
131+ it ( 'when there are only migrated connections' , async function ( ) {
132+ const connectionInfo1 = getConnectionInfo ( ) ;
133+ const connectionInfo2 = getConnectionInfo ( ) ;
134+
135+ await writeFakeConnection ( tmpDir , {
136+ _id : connectionInfo1 . id ,
137+ connectionInfo : connectionInfo1 ,
138+ version : 1 ,
139+ } ) ;
140+ await writeFakeConnection ( tmpDir , {
141+ _id : connectionInfo2 . id ,
142+ connectionInfo : connectionInfo2 ,
143+ version : 1 ,
144+ } ) ;
145+
146+ const encryptSecretsSpy = sandbox . spy (
147+ ConnectionStorage ,
148+ 'encryptSecrets' as any
149+ ) ;
150+ const getKeytarCredentialsSpy = sandbox . spy (
151+ ConnectionStorage ,
152+ 'getKeytarCredentials' as any
153+ ) ;
154+
155+ await ConnectionStorage . migrateToSafeStorage ( ) ;
156+
157+ expect (
158+ encryptSecretsSpy . called ,
159+ 'it does not try to encrypt'
160+ ) . to . be . false ;
161+ expect (
162+ getKeytarCredentialsSpy . called ,
163+ 'it does not try to get secrets from keychain'
164+ ) . to . be . false ;
165+
166+ const expectedConnection1 = await readConnection (
167+ tmpDir ,
168+ connectionInfo1 . id
169+ ) ;
170+ const expectedConnection2 = await readConnection (
171+ tmpDir ,
172+ connectionInfo2 . id
173+ ) ;
174+
175+ expect ( expectedConnection1 ) . to . deep . equal ( {
176+ _id : connectionInfo1 . id ,
177+ connectionInfo : connectionInfo1 ,
178+ version : 1 ,
179+ } ) ;
180+
181+ expect ( expectedConnection2 ) . to . deep . equal ( {
182+ _id : connectionInfo2 . id ,
183+ connectionInfo : connectionInfo2 ,
184+ version : 1 ,
185+ } ) ;
186+ } ) ;
187+ } ) ;
188+
189+ context ( 'migrates connections' , function ( ) {
190+ it ( 'when there are connections and secrets in keychain' , async function ( ) {
191+ const connectionInfo1 = getConnectionInfo ( ) ;
192+ const connectionInfo2 = getConnectionInfo ( ) ;
193+ await writeFakeConnection ( tmpDir , {
194+ connectionInfo : connectionInfo1 ,
195+ } ) ;
196+ await writeFakeConnection ( tmpDir , {
197+ connectionInfo : connectionInfo2 ,
198+ } ) ;
199+
200+ // Keytar stub
201+ sandbox . stub ( ConnectionStorage , 'getKeytarCredentials' as any ) . returns ( {
202+ [ connectionInfo1 . id ] : {
203+ password : 'password1' ,
204+ } ,
205+ [ connectionInfo2 . id ] : {
206+ password : 'password2' ,
207+ } ,
208+ } ) ;
209+
210+ // safeStorage.encryptString stub
211+ sandbox
212+ . stub ( ConnectionStorage , 'encryptSecrets' as any )
213+ . returns ( 'encrypted-password' ) ;
214+
215+ await ConnectionStorage . migrateToSafeStorage ( ) ;
216+
217+ const expectedConnection1 = await readConnection (
218+ tmpDir ,
219+ connectionInfo1 . id
220+ ) ;
221+ const expectedConnection2 = await readConnection (
222+ tmpDir ,
223+ connectionInfo2 . id
224+ ) ;
225+
226+ expect ( expectedConnection1 ) . to . deep . equal ( {
227+ _id : connectionInfo1 . id ,
228+ connectionInfo : connectionInfo1 ,
229+ connectionSecrets : 'encrypted-password' ,
230+ version : 1 ,
231+ } ) ;
232+ expect ( expectedConnection2 ) . to . deep . equal ( {
233+ _id : connectionInfo2 . id ,
234+ connectionInfo : connectionInfo2 ,
235+ connectionSecrets : 'encrypted-password' ,
236+ version : 1 ,
237+ } ) ;
238+ } ) ;
239+ it ( 'when there are connections and no secrets in keychain' , async function ( ) {
240+ const connectionInfo1 = getConnectionInfo ( ) ;
241+ const connectionInfo2 = getConnectionInfo ( ) ;
242+ await writeFakeConnection ( tmpDir , {
243+ connectionInfo : connectionInfo1 ,
244+ } ) ;
245+ await writeFakeConnection ( tmpDir , {
246+ connectionInfo : connectionInfo2 ,
247+ } ) ;
248+
249+ // Keytar fake
250+ sandbox
251+ . stub ( ConnectionStorage , 'getKeytarCredentials' as any )
252+ . returns ( { } ) ;
253+
254+ // Since there're no secrets in keychain, we do not expect to call safeStorage.encryptString
255+ // and connection.connectionSecrets should be undefined
256+
257+ await ConnectionStorage . migrateToSafeStorage ( ) ;
258+
259+ const expectedConnection1 = await readConnection (
260+ tmpDir ,
261+ connectionInfo1 . id
262+ ) ;
263+ const expectedConnection2 = await readConnection (
264+ tmpDir ,
265+ connectionInfo2 . id
266+ ) ;
267+
268+ expect ( expectedConnection1 ) . to . deep . equal ( {
269+ _id : connectionInfo1 . id ,
270+ connectionInfo : connectionInfo1 ,
271+ version : 1 ,
272+ } ) ;
273+ expect ( expectedConnection2 ) . to . deep . equal ( {
274+ _id : connectionInfo2 . id ,
275+ connectionInfo : connectionInfo2 ,
276+ version : 1 ,
277+ } ) ;
278+ } ) ;
279+
280+ it ( 'when there are any unmigrated connections' , async function ( ) {
281+ const connectionInfo1 = getConnectionInfo ( ) ;
282+ const connectionInfo2 = getConnectionInfo ( ) ;
283+ await writeFakeConnection ( tmpDir , {
284+ _id : connectionInfo1 . id ,
285+ connectionInfo : connectionInfo1 ,
286+ version : 1 ,
287+ } ) ;
288+ await writeFakeConnection ( tmpDir , {
289+ connectionInfo : connectionInfo2 ,
290+ } ) ;
291+
292+ // Keytar stub
293+ sandbox
294+ . stub ( ConnectionStorage , 'getKeytarCredentials' as any )
295+ . returns ( { } ) ;
296+
297+ await ConnectionStorage . migrateToSafeStorage ( ) ;
298+
299+ const expectedConnection1 = await readConnection (
300+ tmpDir ,
301+ connectionInfo1 . id
302+ ) ;
303+ const expectedConnection2 = await readConnection (
304+ tmpDir ,
305+ connectionInfo2 . id
306+ ) ;
307+
308+ expect ( expectedConnection1 ) . to . deep . equal ( {
309+ _id : connectionInfo1 . id ,
310+ connectionInfo : connectionInfo1 ,
311+ version : 1 ,
312+ } ) ;
313+ expect ( expectedConnection2 ) . to . deep . equal ( {
314+ _id : connectionInfo2 . id ,
315+ connectionInfo : connectionInfo2 ,
316+ version : 1 ,
317+ } ) ;
318+ } ) ;
319+ } ) ;
320+ } ) ;
321+
78322 describe ( 'loadAll' , function ( ) {
79323 it ( 'should load an empty array with no connections' , async function ( ) {
80324 const connections = await ConnectionStorage . loadAll ( ) ;
@@ -463,27 +707,12 @@ describe('ConnectionStorage', function () {
463707 } ) ;
464708
465709 it ( 'returns true if there are favorite legacy connections' , async function ( ) {
466- const _id = new UUID ( ) . toString ( ) ;
467-
468- // Save a legacy connection (connection without connectionInfo)
469- const filePath = getConnectionFilePath ( tmpDir , _id ) ;
470- // As we are saving a legacy connection here, we can not use Storage.save as
471- // it requries connectionInfo. And the internals of fs ensure the subdir exists,
472- // here we are creating it manually.
473- await fs . mkdir ( path . join ( tmpDir , 'Connections' ) , { recursive : true } ) ;
474- await fs . writeFile (
475- filePath ,
476- JSON . stringify ( {
477- _id,
478- isFavorite : true ,
479- name : 'Local 1' ,
480- hosts : [ { host : 'localhost' , port : 27017 } ] ,
481- readPreference : 'primary' ,
482- port : 27017 ,
483- hostname : 'localhost' ,
484- } )
485- ) ;
486-
710+ // Write a legacy connection (connection without connectionInfo, which is favorite and has a name)
711+ await writeFakeConnection ( tmpDir , {
712+ _id : new UUID ( ) . toString ( ) ,
713+ isFavorite : true ,
714+ name : 'Local 1' ,
715+ } ) ;
487716 const getLegacyConnections =
488717 await ConnectionStorage . getLegacyConnections ( ) ;
489718 expect ( getLegacyConnections ) . to . deep . equal ( [ { name : 'Local 1' } ] ) ;
@@ -583,18 +812,8 @@ describe('ConnectionStorage', function () {
583812 } ) ;
584813
585814 describe ( 'supports connections from older version of compass' , function ( ) {
586- const storeConnection = async ( connection : any ) => {
587- const connectionFolder = path . join ( tmpDir , 'Connections' ) ;
588- const connectionPath = path . join (
589- connectionFolder ,
590- `${ connection . _id } .json`
591- ) ;
592- await fs . mkdir ( connectionFolder , { recursive : true } ) ;
593- await fs . writeFile ( connectionPath , JSON . stringify ( connection ) ) ;
594- } ;
595-
596815 it ( 'correctly identifies connection as legacy connection' , async function ( ) {
597- await storeConnection ( connection1270 ) ;
816+ await writeFakeConnection ( tmpDir , connection1270 ) ;
598817 const expectedConnection = await ConnectionStorage . load ( {
599818 id : connection1270 . _id ,
600819 } ) ;
@@ -612,7 +831,7 @@ describe('ConnectionStorage', function () {
612831
613832 for ( const version in connections ) {
614833 const connection = connections [ version ] ;
615- await storeConnection ( connection ) ;
834+ await writeFakeConnection ( tmpDir , connection ) ;
616835 const expectedConnection = await ConnectionStorage . load ( {
617836 id : connection . _id ,
618837 } ) ;
0 commit comments