1 //  Copyright (c) 2020 650 Industries, Inc. All rights reserved.
2 
3 import ExpoModulesTestCore
4 
5 @testable import EXUpdates
6 
7 import EXManifests
8 
9 class ReaperSelectionPolicyDevelopmentClientSpec : ExpoSpec {
specnull10   override func spec() {
11     var update1: Update!
12     var update2: Update!
13     var update3: Update!
14     var update4: Update!
15     var update5: Update!
16     var selectionPolicy: ReaperSelectionPolicy!
17 
18     beforeEach {
19       let runtimeVersion = "1.0"
20       let database = UpdatesDatabase()
21 
22       // test updates with different scopes to ensure this policy ignores scopes
23       update1 = Update(
24         manifest: ManifestFactory.manifest(forManifestJSON: [:]),
25         config: UpdatesConfig.config(fromDictionary: [
26           UpdatesConfig.EXUpdatesConfigScopeKeyKey: "scope1"
27         ]),
28         database: database,
29         updateId: UUID(),
30         scopeKey: "scope1",
31         commitTime: Date(timeIntervalSince1970: 1608667851),
32         runtimeVersion: runtimeVersion,
33         keep: true,
34         status: .StatusReady,
35         isDevelopmentMode: false,
36         assetsFromManifest: []
37       )
38       update2 = Update(
39         manifest: ManifestFactory.manifest(forManifestJSON: [:]),
40         config: UpdatesConfig.config(fromDictionary: [
41           UpdatesConfig.EXUpdatesConfigScopeKeyKey: "scope2"
42         ]),
43         database: database,
44         updateId: UUID(),
45         scopeKey: "scope2",
46         commitTime: Date(timeIntervalSince1970: 1608667852),
47         runtimeVersion: runtimeVersion,
48         keep: true,
49         status: .StatusReady,
50         isDevelopmentMode: false,
51         assetsFromManifest: []
52       )
53       update3 = Update(
54         manifest: ManifestFactory.manifest(forManifestJSON: [:]),
55         config: UpdatesConfig.config(fromDictionary: [
56           UpdatesConfig.EXUpdatesConfigScopeKeyKey: "scope3"
57         ]),
58         database: database,
59         updateId: UUID(),
60         scopeKey: "scope3",
61         commitTime: Date(timeIntervalSince1970: 1608667853),
62         runtimeVersion: runtimeVersion,
63         keep: true,
64         status: .StatusReady,
65         isDevelopmentMode: false,
66         assetsFromManifest: []
67       )
68       update4 = Update(
69         manifest: ManifestFactory.manifest(forManifestJSON: [:]),
70         config: UpdatesConfig.config(fromDictionary: [
71           UpdatesConfig.EXUpdatesConfigScopeKeyKey: "scope4"
72         ]),
73         database: database,
74         updateId: UUID(),
75         scopeKey: "scope4",
76         commitTime: Date(timeIntervalSince1970: 1608667854),
77         runtimeVersion: runtimeVersion,
78         keep: true,
79         status: .StatusReady,
80         isDevelopmentMode: false,
81         assetsFromManifest: []
82       )
83       update5 = Update(
84         manifest: ManifestFactory.manifest(forManifestJSON: [:]),
85         config: UpdatesConfig.config(fromDictionary: [
86           UpdatesConfig.EXUpdatesConfigScopeKeyKey: "scope5"
87         ]),
88         database: database,
89         updateId: UUID(),
90         scopeKey: "scope5",
91         commitTime: Date(timeIntervalSince1970: 1608667855),
92         runtimeVersion: runtimeVersion,
93         keep: true,
94         status: .StatusReady,
95         isDevelopmentMode: false,
96         assetsFromManifest: []
97       )
98 
99       // for readability/writability, test with a policy that keeps only 3 updates;
100       // the actual functionality is independent of the number
101       selectionPolicy = ReaperSelectionPolicyDevelopmentClient.init(maxUpdatesToKeep: 3)
102     }
103 
104     describe("updates to delete") {
105       it("basic case") {
106         update1.lastAccessed = Date(timeIntervalSince1970: 1619569811)
107         update2.lastAccessed = Date(timeIntervalSince1970: 1619569812)
108         update3.lastAccessed = Date(timeIntervalSince1970: 1619569813)
109         update4.lastAccessed = Date(timeIntervalSince1970: 1619569814)
110         update5.lastAccessed = Date(timeIntervalSince1970: 1619569815)
111 
112         // the order of the array shouldn't matter
113         let updatesToDelete = selectionPolicy.updatesToDelete(withLaunchedUpdate: update5, updates: [update2, update5, update4, update1, update3], filters: nil)
114         expect(updatesToDelete.count) == 2
115         expect(updatesToDelete.contains(update1)) == true
116         expect(updatesToDelete.contains(update2)) == true
117       }
118 
119       it("same last accessed date") {
120         // if multiple updates have the same lastAccessed date, should use commitTime to determine
121         // which updates to delete
122         update1.lastAccessed = Date(timeIntervalSince1970: 1619569810)
123         update2.lastAccessed = Date(timeIntervalSince1970: 1619569810)
124         update3.lastAccessed = Date(timeIntervalSince1970: 1619569810)
125         update4.lastAccessed = Date(timeIntervalSince1970: 1619569810)
126 
127         let updatesToDelete = selectionPolicy.updatesToDelete(withLaunchedUpdate: update4, updates: [update3, update4, update1, update2], filters: nil)
128         expect(updatesToDelete.count) == 1
129         expect(updatesToDelete.contains(update1)) == true
130       }
131 
132       it("launched update is oldest") {
133         // if the least recently accessed update happens to be launchedUpdate, delete instead the next
134         // least recently accessed update
135         update1.lastAccessed = Date(timeIntervalSince1970: 1619569811)
136         update2.lastAccessed = Date(timeIntervalSince1970: 1619569812)
137         update3.lastAccessed = Date(timeIntervalSince1970: 1619569813)
138         update4.lastAccessed = Date(timeIntervalSince1970: 1619569814)
139 
140         let updatesToDelete = selectionPolicy.updatesToDelete(withLaunchedUpdate: update1, updates: [update1, update2, update3, update4], filters: nil)
141         expect(updatesToDelete.count) == 1
142         expect(updatesToDelete.contains(update2)) == true
143       }
144 
145       it("below max number") {
146         // no need to delete any updates if we have <= the max number of updates
147         let updatesToDeleteWith2Total = selectionPolicy.updatesToDelete(withLaunchedUpdate: update2, updates: [update1, update2], filters: nil)
148         let updatesToDeleteWith3Total = selectionPolicy.updatesToDelete(withLaunchedUpdate: update3, updates: [update1, update2, update3], filters: nil)
149         expect(updatesToDeleteWith2Total.count) == 0
150         expect(updatesToDeleteWith3Total.count) == 0
151       }
152     }
153   }
154 }
155