Skip to content

Commit fc0f5b0

Browse files
authoredFeb 19, 2025··
refactor: merge session & settings abstractions (#46)
Unfortunately necessary for #52, as we need the HTTP headers from settings when creating the protocol configuration (which is derived from the session). The class retains all the same invariants as before.
1 parent 250017b commit fc0f5b0

17 files changed

+119
-176
lines changed
 

‎Coder Desktop/Coder Desktop/Coder_DesktopApp.swift

+8-12
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@ struct DesktopApp: App {
1111
EmptyView()
1212
}
1313
Window("Sign In", id: Windows.login.rawValue) {
14-
LoginForm<SecureSession>()
15-
.environmentObject(appDelegate.session)
16-
.environmentObject(appDelegate.settings)
14+
LoginForm()
15+
.environmentObject(appDelegate.state)
1716
}
1817
.windowResizability(.contentSize)
1918
SwiftUI.Settings {
2019
SettingsView<CoderVPNService>()
2120
.environmentObject(appDelegate.vpn)
22-
.environmentObject(appDelegate.settings)
21+
.environmentObject(appDelegate.state)
2322
}
2423
.windowResizability(.contentSize)
2524
}
@@ -29,28 +28,25 @@ struct DesktopApp: App {
2928
class AppDelegate: NSObject, NSApplicationDelegate {
3029
private var menuBarExtra: FluidMenuBarExtra?
3130
let vpn: CoderVPNService
32-
let session: SecureSession
33-
let settings: Settings
31+
let state: AppState
3432

3533
override init() {
3634
vpn = CoderVPNService()
37-
settings = Settings()
38-
session = SecureSession(onChange: vpn.configureTunnelProviderProtocol)
35+
state = AppState(onChange: vpn.configureTunnelProviderProtocol)
3936
}
4037

4138
func applicationDidFinishLaunching(_: Notification) {
4239
menuBarExtra = FluidMenuBarExtra(title: "Coder Desktop", image: "MenuBarIcon") {
43-
VPNMenu<CoderVPNService, SecureSession>().frame(width: 256)
40+
VPNMenu<CoderVPNService>().frame(width: 256)
4441
.environmentObject(self.vpn)
45-
.environmentObject(self.session)
46-
.environmentObject(self.settings)
42+
.environmentObject(self.state)
4743
}
4844
}
4945

5046
// This function MUST eventually call `NSApp.reply(toApplicationShouldTerminate: true)`
5147
// or return `.terminateNow`
5248
func applicationShouldTerminate(_: NSApplication) -> NSApplication.TerminateReply {
53-
if !settings.stopVPNOnQuit { return .terminateNow }
49+
if !state.stopVPNOnQuit { return .terminateNow }
5450
Task {
5551
await vpn.stop()
5652
NSApp.reply(toApplicationShouldTerminate: true)

‎Coder Desktop/Coder Desktop/Preview Content/PreviewSession.swift

-29
This file was deleted.

‎Coder Desktop/Coder Desktop/State.swift

+43-41
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,20 @@ import KeychainAccess
44
import NetworkExtension
55
import SwiftUI
66

7-
protocol Session: ObservableObject {
8-
var hasSession: Bool { get }
9-
var baseAccessURL: URL? { get }
10-
var sessionToken: String? { get }
11-
12-
func store(baseAccessURL: URL, sessionToken: String)
13-
func clear()
14-
func tunnelProviderProtocol() -> NETunnelProviderProtocol?
15-
}
16-
17-
class SecureSession: ObservableObject, Session {
7+
class AppState: ObservableObject {
188
let appId = Bundle.main.bundleIdentifier!
199

2010
// Stored in UserDefaults
2111
@Published private(set) var hasSession: Bool {
2212
didSet {
13+
guard persistent else { return }
2314
UserDefaults.standard.set(hasSession, forKey: Keys.hasSession)
2415
}
2516
}
2617

2718
@Published private(set) var baseAccessURL: URL? {
2819
didSet {
20+
guard persistent else { return }
2921
UserDefaults.standard.set(baseAccessURL, forKey: Keys.baseAccessURL)
3022
}
3123
}
@@ -37,6 +29,27 @@ class SecureSession: ObservableObject, Session {
3729
}
3830
}
3931

32+
@Published var useLiteralHeaders: Bool = UserDefaults.standard.bool(forKey: Keys.useLiteralHeaders) {
33+
didSet {
34+
guard persistent else { return }
35+
UserDefaults.standard.set(useLiteralHeaders, forKey: Keys.useLiteralHeaders)
36+
}
37+
}
38+
39+
@Published var literalHeaders: [LiteralHeader] {
40+
didSet {
41+
guard persistent else { return }
42+
try? UserDefaults.standard.set(JSONEncoder().encode(literalHeaders), forKey: Keys.literalHeaders)
43+
}
44+
}
45+
46+
@Published var stopVPNOnQuit: Bool = UserDefaults.standard.bool(forKey: Keys.stopVPNOnQuit) {
47+
didSet {
48+
guard persistent else { return }
49+
UserDefaults.standard.set(stopVPNOnQuit, forKey: Keys.stopVPNOnQuit)
50+
}
51+
}
52+
4053
func tunnelProviderProtocol() -> NETunnelProviderProtocol? {
4154
if !hasSession { return nil }
4255
let proto = NETunnelProviderProtocol()
@@ -49,37 +62,50 @@ class SecureSession: ObservableObject, Session {
4962
}
5063

5164
private let keychain: Keychain
65+
private let persistent: Bool
5266

5367
let onChange: ((NETunnelProviderProtocol?) -> Void)?
5468

55-
public init(onChange: ((NETunnelProviderProtocol?) -> Void)? = nil) {
69+
public init(onChange: ((NETunnelProviderProtocol?) -> Void)? = nil,
70+
persistent: Bool = true)
71+
{
72+
self.persistent = persistent
5673
self.onChange = onChange
5774
keychain = Keychain(service: Bundle.main.bundleIdentifier!)
58-
_hasSession = Published(initialValue: UserDefaults.standard.bool(forKey: Keys.hasSession))
59-
_baseAccessURL = Published(initialValue: UserDefaults.standard.url(forKey: Keys.baseAccessURL))
75+
_hasSession = Published(initialValue: persistent ? UserDefaults.standard.bool(forKey: Keys.hasSession) : false)
76+
_baseAccessURL = Published(
77+
initialValue: persistent ? UserDefaults.standard.url(forKey: Keys.baseAccessURL) : nil
78+
)
79+
_literalHeaders = Published(
80+
initialValue: persistent ? UserDefaults.standard.data(
81+
forKey: Keys.literalHeaders
82+
).flatMap { try? JSONDecoder().decode([LiteralHeader].self, from: $0) } ?? [] : []
83+
)
6084
if hasSession {
6185
_sessionToken = Published(initialValue: keychainGet(for: Keys.sessionToken))
6286
}
6387
}
6488

65-
public func store(baseAccessURL: URL, sessionToken: String) {
89+
public func login(baseAccessURL: URL, sessionToken: String) {
6690
hasSession = true
6791
self.baseAccessURL = baseAccessURL
6892
self.sessionToken = sessionToken
6993
if let onChange { onChange(tunnelProviderProtocol()) }
7094
}
7195

72-
public func clear() {
96+
public func clearSession() {
7397
hasSession = false
7498
sessionToken = nil
7599
if let onChange { onChange(tunnelProviderProtocol()) }
76100
}
77101

78102
private func keychainGet(for key: String) -> String? {
79-
try? keychain.getString(key)
103+
guard persistent else { return nil }
104+
return try? keychain.getString(key)
80105
}
81106

82107
private func keychainSet(_ value: String?, for key: String) {
108+
guard persistent else { return }
83109
if let value {
84110
try? keychain.set(value, key: key)
85111
} else {
@@ -91,31 +117,7 @@ class SecureSession: ObservableObject, Session {
91117
static let hasSession = "hasSession"
92118
static let baseAccessURL = "baseAccessURL"
93119
static let sessionToken = "sessionToken"
94-
}
95-
}
96-
97-
class Settings: ObservableObject {
98-
private let store: UserDefaults
99-
@AppStorage(Keys.useLiteralHeaders) var useLiteralHeaders = false
100120

101-
@Published var literalHeaders: [LiteralHeader] {
102-
didSet {
103-
try? store.set(JSONEncoder().encode(literalHeaders), forKey: Keys.literalHeaders)
104-
}
105-
}
106-
107-
@AppStorage(Keys.stopVPNOnQuit) var stopVPNOnQuit = true
108-
109-
init(store: UserDefaults = .standard) {
110-
self.store = store
111-
_literalHeaders = Published(
112-
initialValue: store.data(
113-
forKey: Keys.literalHeaders
114-
).flatMap { try? JSONDecoder().decode([LiteralHeader].self, from: $0) } ?? []
115-
)
116-
}
117-
118-
enum Keys {
119121
static let useLiteralHeaders = "UseLiteralHeaders"
120122
static let literalHeaders = "LiteralHeaders"
121123
static let stopVPNOnQuit = "StopVPNOnQuit"

‎Coder Desktop/Coder Desktop/Views/Agents.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import SwiftUI
22

3-
struct Agents<VPN: VPNService, S: Session>: View {
3+
struct Agents<VPN: VPNService>: View {
44
@EnvironmentObject var vpn: VPN
5-
@EnvironmentObject var session: S
5+
@EnvironmentObject var state: AppState
66
@State private var viewAll = false
77
private let defaultVisibleRows = 5
88

@@ -15,7 +15,7 @@ struct Agents<VPN: VPNService, S: Session>: View {
1515
let items = vpn.menuState.sorted
1616
let visibleItems = viewAll ? items[...] : items.prefix(defaultVisibleRows)
1717
ForEach(visibleItems, id: \.id) { agent in
18-
MenuItemView(item: agent, baseAccessURL: session.baseAccessURL!)
18+
MenuItemView(item: agent, baseAccessURL: state.baseAccessURL!)
1919
.padding(.horizontal, Theme.Size.trayMargin)
2020
}
2121
if items.count == 0 {

‎Coder Desktop/Coder Desktop/Views/AuthButton.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import SwiftUI
22

3-
struct AuthButton<VPN: VPNService, S: Session>: View {
4-
@EnvironmentObject var session: S
3+
struct AuthButton<VPN: VPNService>: View {
4+
@EnvironmentObject var state: AppState
55
@EnvironmentObject var vpn: VPN
66
@Environment(\.openWindow) var openWindow
77

88
var body: some View {
99
Button {
10-
if session.hasSession {
10+
if state.hasSession {
1111
Task {
1212
await vpn.stop()
13-
session.clear()
13+
state.clearSession()
1414
}
1515
} else {
1616
openWindow(id: .login)
1717
}
1818
} label: {
1919
ButtonRowView {
20-
Text(session.hasSession ? "Sign out" : "Sign in")
20+
Text(state.hasSession ? "Sign out" : "Sign in")
2121
}
2222
}.buttonStyle(.plain)
2323
}

‎Coder Desktop/Coder Desktop/Views/LoginForm.swift

+7-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import CoderSDK
22
import SwiftUI
33

4-
struct LoginForm<S: Session>: View {
5-
@EnvironmentObject var session: S
6-
@EnvironmentObject var settings: Settings
4+
struct LoginForm: View {
5+
@EnvironmentObject var state: AppState
76
@Environment(\.dismiss) private var dismiss
87

98
@State private var baseAccessURL: String = ""
@@ -38,7 +37,7 @@ struct LoginForm<S: Session>: View {
3837
}
3938
.animation(.easeInOut, value: currentPage)
4039
.onAppear {
41-
baseAccessURL = session.baseAccessURL?.absoluteString ?? baseAccessURL
40+
baseAccessURL = state.baseAccessURL?.absoluteString ?? baseAccessURL
4241
sessionToken = ""
4342
}
4443
.alert("Error", isPresented: Binding(
@@ -72,14 +71,14 @@ struct LoginForm<S: Session>: View {
7271
}
7372
loading = true
7473
defer { loading = false }
75-
let client = Client(url: url, token: sessionToken, headers: settings.literalHeaders.map { $0.toSDKHeader() })
74+
let client = Client(url: url, token: sessionToken, headers: state.literalHeaders.map { $0.toSDKHeader() })
7675
do {
7776
_ = try await client.user("me")
7877
} catch {
7978
loginError = .failedAuth(error)
8079
return
8180
}
82-
session.store(baseAccessURL: url, sessionToken: sessionToken)
81+
state.login(baseAccessURL: url, sessionToken: sessionToken)
8382
dismiss()
8483
}
8584

@@ -219,7 +218,7 @@ enum LoginField: Hashable {
219218

220219
#if DEBUG
221220
#Preview {
222-
LoginForm<PreviewSession>()
223-
.environmentObject(PreviewSession())
221+
LoginForm()
222+
.environmentObject(AppState())
224223
}
225224
#endif

‎Coder Desktop/Coder Desktop/Views/Settings/GeneralTab.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import LaunchAtLogin
22
import SwiftUI
33

44
struct GeneralTab: View {
5-
@EnvironmentObject var settings: Settings
5+
@EnvironmentObject var state: AppState
66
var body: some View {
77
Form {
88
Section {
99
LaunchAtLogin.Toggle("Launch at Login")
1010
}
1111
Section {
12-
Toggle(isOn: $settings.stopVPNOnQuit) {
12+
Toggle(isOn: $state.stopVPNOnQuit) {
1313
Text("Stop VPN on Quit")
1414
}
1515
}

‎Coder Desktop/Coder Desktop/Views/Settings/LiteralHeaderModal.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import SwiftUI
33
struct LiteralHeaderModal: View {
44
var existingHeader: LiteralHeader?
55

6-
@EnvironmentObject var settings: Settings
6+
@EnvironmentObject var state: AppState
77
@Environment(\.dismiss) private var dismiss
88

99
@State private var header: String = ""
@@ -35,11 +35,11 @@ struct LiteralHeaderModal: View {
3535
func submit() {
3636
defer { dismiss() }
3737
if let existingHeader {
38-
settings.literalHeaders.removeAll { $0 == existingHeader }
38+
state.literalHeaders.removeAll { $0 == existingHeader }
3939
}
4040
let newHeader = LiteralHeader(header: header, value: value)
41-
if !settings.literalHeaders.contains(newHeader) {
42-
settings.literalHeaders.append(newHeader)
41+
if !state.literalHeaders.contains(newHeader) {
42+
state.literalHeaders.append(newHeader)
4343
}
4444
}
4545
}

‎Coder Desktop/Coder Desktop/Views/Settings/LiteralHeadersSection.swift

+7-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import SwiftUI
22

33
struct LiteralHeadersSection<VPN: VPNService>: View {
44
@EnvironmentObject var vpn: VPN
5-
@EnvironmentObject var settings: Settings
5+
@EnvironmentObject var state: AppState
66

77
@State private var selectedHeader: LiteralHeader.ID?
88
@State private var editingHeader: LiteralHeader?
@@ -12,17 +12,17 @@ struct LiteralHeadersSection<VPN: VPNService>: View {
1212

1313
var body: some View {
1414
Section {
15-
Toggle(isOn: settings.$useLiteralHeaders) {
15+
Toggle(isOn: $state.useLiteralHeaders) {
1616
Text("HTTP Headers")
1717
Text("When enabled, these headers will be included on all outgoing HTTP requests.")
1818
if vpn.state != .disabled { Text("Cannot be modified while Coder VPN is enabled.") }
1919
}
2020
.controlSize(.large)
2121

22-
Table(settings.literalHeaders, selection: $selectedHeader) {
22+
Table(state.literalHeaders, selection: $selectedHeader) {
2323
TableColumn("Header", value: \.header)
2424
TableColumn("Value", value: \.value)
25-
}.opacity(settings.useLiteralHeaders ? 1 : 0.5)
25+
}.opacity(state.useLiteralHeaders ? 1 : 0.5)
2626
.frame(minWidth: 400, minHeight: 200)
2727
.padding(.bottom, 25)
2828
.overlay(alignment: .bottom) {
@@ -37,7 +37,7 @@ struct LiteralHeadersSection<VPN: VPNService>: View {
3737
}
3838
Divider()
3939
Button {
40-
settings.literalHeaders.removeAll { $0.id == selectedHeader }
40+
state.literalHeaders.removeAll { $0.id == selectedHeader }
4141
selectedHeader = nil
4242
} label: {
4343
Image(systemName: "minus")
@@ -53,10 +53,10 @@ struct LiteralHeadersSection<VPN: VPNService>: View {
5353
.contextMenu(forSelectionType: LiteralHeader.ID.self, menu: { _ in },
5454
primaryAction: { selectedHeaders in
5555
if let firstHeader = selectedHeaders.first {
56-
editingHeader = settings.literalHeaders.first(where: { $0.id == firstHeader })
56+
editingHeader = state.literalHeaders.first(where: { $0.id == firstHeader })
5757
}
5858
})
59-
.disabled(!settings.useLiteralHeaders)
59+
.disabled(!state.useLiteralHeaders)
6060
}
6161
.sheet(isPresented: $addingNewHeader) {
6262
LiteralHeaderModal()

‎Coder Desktop/Coder Desktop/Views/VPNMenu.swift

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import SwiftUI
22

3-
struct VPNMenu<VPN: VPNService, S: Session>: View {
3+
struct VPNMenu<VPN: VPNService>: View {
44
@EnvironmentObject var vpn: VPN
5-
@EnvironmentObject var session: S
5+
@EnvironmentObject var state: AppState
66
@Environment(\.openSettings) private var openSettings
77

88
// There appears to be a race between the VPN service reporting itself as disconnected,
@@ -38,17 +38,17 @@ struct VPNMenu<VPN: VPNService, S: Session>: View {
3838
Text("Workspaces")
3939
.font(.headline)
4040
.foregroundColor(.gray)
41-
VPNState<VPN, S>()
41+
VPNState<VPN>()
4242
}.padding([.horizontal, .top], Theme.Size.trayInset)
43-
Agents<VPN, S>()
43+
Agents<VPN>()
4444
// Trailing stack
4545
VStack(alignment: .leading, spacing: 3) {
4646
TrayDivider()
4747
if vpn.state == .connected, !vpn.menuState.invalidAgents.isEmpty {
4848
InvalidAgentsButton<VPN>()
4949
}
50-
if session.hasSession {
51-
Link(destination: session.baseAccessURL!.appending(path: "templates")) {
50+
if state.hasSession {
51+
Link(destination: state.baseAccessURL!.appending(path: "templates")) {
5252
ButtonRowView {
5353
Text("Create workspace")
5454
}
@@ -62,7 +62,7 @@ struct VPNMenu<VPN: VPNService, S: Session>: View {
6262
ButtonRowView { Text("Approve in System Settings") }
6363
}.buttonStyle(.plain)
6464
} else {
65-
AuthButton<VPN, S>()
65+
AuthButton<VPN>()
6666
}
6767
Button {
6868
openSettings()
@@ -88,13 +88,13 @@ struct VPNMenu<VPN: VPNService, S: Session>: View {
8888
}.padding([.horizontal, .bottom], Theme.Size.trayMargin)
8989
}.padding(.bottom, Theme.Size.trayMargin)
9090
.environmentObject(vpn)
91-
.environmentObject(session)
91+
.environmentObject(state)
9292
.onReceive(inspection.notice) { inspection.visit(self, $0) } // ViewInspector
9393
}
9494

9595
private var vpnDisabled: Bool {
9696
waitCleanup ||
97-
!session.hasSession ||
97+
!state.hasSession ||
9898
vpn.state == .connecting ||
9999
vpn.state == .disconnecting ||
100100
vpn.state == .failed(.systemExtensionError(.needsUserApproval))
@@ -120,8 +120,8 @@ func openSystemExtensionSettings() {
120120

121121
#if DEBUG
122122
#Preview {
123-
VPNMenu<PreviewVPN, PreviewSession>().frame(width: 256)
123+
VPNMenu<PreviewVPN>().frame(width: 256)
124124
.environmentObject(PreviewVPN())
125-
.environmentObject(PreviewSession())
125+
.environmentObject(AppState(persistent: false))
126126
}
127127
#endif

‎Coder Desktop/Coder Desktop/Views/VPNState.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import SwiftUI
22

3-
struct VPNState<VPN: VPNService, S: Session>: View {
3+
struct VPNState<VPN: VPNService>: View {
44
@EnvironmentObject var vpn: VPN
5-
@EnvironmentObject var session: S
5+
@EnvironmentObject var state: AppState
66

77
let inspection = Inspection<Self>()
88

99
var body: some View {
1010
Group {
11-
switch (vpn.state, session.hasSession) {
11+
switch (vpn.state, state.hasSession) {
1212
case (.failed(.systemExtensionError(.needsUserApproval)), _):
1313
Text("Awaiting System Extension approval")
1414
.font(.body)

‎Coder Desktop/Coder DesktopTests/AgentsTests.swift

+6-5
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ import ViewInspector
77
@Suite(.timeLimit(.minutes(1)))
88
struct AgentsTests {
99
let vpn: MockVPNService
10-
let session: MockSession
11-
let sut: Agents<MockVPNService, MockSession>
10+
let state: AppState
11+
let sut: Agents<MockVPNService>
1212
let view: any View
1313

1414
init() {
1515
vpn = MockVPNService()
16-
session = MockSession()
17-
sut = Agents<MockVPNService, MockSession>()
18-
view = sut.environmentObject(vpn).environmentObject(session)
16+
state = AppState(persistent: false)
17+
state.login(baseAccessURL: URL(string: "https://coder.example.com")!, sessionToken: "fake-token")
18+
sut = Agents<MockVPNService>()
19+
view = sut.environmentObject(vpn).environmentObject(state)
1920
}
2021

2122
private func createMockAgents(count: Int, status: AgentStatus = .okay) -> [UUID: Agent] {

‎Coder Desktop/Coder DesktopTests/LiteralHeadersSettingTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ struct LiteralHeadersSettingTests {
1515
sut = LiteralHeadersSection<MockVPNService>()
1616
let store = UserDefaults(suiteName: #file)!
1717
store.removePersistentDomain(forName: #file)
18-
view = sut.environmentObject(vpn).environmentObject(Settings(store: store))
18+
view = sut.environmentObject(vpn).environmentObject(AppState(persistent: false))
1919
}
2020

2121
@Test

‎Coder Desktop/Coder DesktopTests/LoginFormTests.swift

+6-6
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ import ViewInspector
88
@MainActor
99
@Suite(.timeLimit(.minutes(1)))
1010
struct LoginTests {
11-
let session: MockSession
12-
let sut: LoginForm<MockSession>
11+
let state: AppState
12+
let sut: LoginForm
1313
let view: any View
1414

1515
init() {
16-
session = MockSession()
17-
sut = LoginForm<MockSession>()
16+
state = AppState(persistent: false)
17+
sut = LoginForm()
1818
let store = UserDefaults(suiteName: #file)!
1919
store.removePersistentDomain(forName: #file)
20-
view = sut.environmentObject(session).environmentObject(Settings(store: store))
20+
view = sut.environmentObject(state)
2121
}
2222

2323
@Test
@@ -120,7 +120,7 @@ struct LoginTests {
120120
try view.find(ViewType.SecureField.self).setInput("valid-token")
121121
try await view.actualView().submit()
122122

123-
#expect(session.hasSession)
123+
#expect(state.hasSession)
124124
}
125125
}
126126
}

‎Coder Desktop/Coder DesktopTests/Util.swift

-25
Original file line numberDiff line numberDiff line change
@@ -25,29 +25,4 @@ class MockVPNService: VPNService, ObservableObject {
2525
func configureTunnelProviderProtocol(proto _: NETunnelProviderProtocol?) {}
2626
}
2727

28-
class MockSession: Session {
29-
@Published
30-
var hasSession: Bool = false
31-
@Published
32-
var sessionToken: String? = "fake-token"
33-
@Published
34-
var baseAccessURL: URL? = URL(string: "https://dev.coder.com")!
35-
36-
func store(baseAccessURL _: URL, sessionToken _: String) {
37-
hasSession = true
38-
baseAccessURL = URL(string: "https://dev.coder.com")!
39-
sessionToken = "fake-token"
40-
}
41-
42-
func clear() {
43-
hasSession = false
44-
sessionToken = nil
45-
baseAccessURL = nil
46-
}
47-
48-
func tunnelProviderProtocol() -> NETunnelProviderProtocol? {
49-
nil
50-
}
51-
}
52-
5328
extension Inspection: @unchecked Sendable, @retroactive InspectionEmissary {}

‎Coder Desktop/Coder DesktopTests/VPNMenuTests.swift

+7-8
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,19 @@ import ViewInspector
77
@Suite(.timeLimit(.minutes(1)))
88
struct VPNMenuTests {
99
let vpn: MockVPNService
10-
let session: MockSession
11-
let sut: VPNMenu<MockVPNService, MockSession>
10+
let state: AppState
11+
let sut: VPNMenu<MockVPNService>
1212
let view: any View
1313

1414
init() {
1515
vpn = MockVPNService()
16-
session = MockSession()
17-
sut = VPNMenu<MockVPNService, MockSession>()
18-
view = sut.environmentObject(vpn).environmentObject(session)
16+
state = AppState(persistent: false)
17+
sut = VPNMenu<MockVPNService>()
18+
view = sut.environmentObject(vpn).environmentObject(state)
1919
}
2020

2121
@Test
2222
func testVPNLoggedOut() async throws {
23-
session.hasSession = false
24-
2523
try await ViewHosting.host(view) {
2624
try await sut.inspection.inspect { view in
2725
let toggle = try view.find(ViewType.Toggle.self)
@@ -104,7 +102,8 @@ struct VPNMenuTests {
104102

105103
@Test
106104
func testOffWhenFailed() async throws {
107-
session.hasSession = true
105+
state.login(baseAccessURL: URL(string: "https://coder.example.com")!, sessionToken: "fake-token")
106+
108107
try await ViewHosting.host(view) {
109108
try await sut.inspection.inspect { view in
110109
let toggle = try view.find(ViewType.Toggle.self)

‎Coder Desktop/Coder DesktopTests/VPNStateTests.swift

+6-6
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ import ViewInspector
77
@Suite(.timeLimit(.minutes(1)))
88
struct VPNStateTests {
99
let vpn: MockVPNService
10-
let session: MockSession
11-
let sut: VPNState<MockVPNService, MockSession>
10+
let state: AppState
11+
let sut: VPNState<MockVPNService>
1212
let view: any View
1313

1414
init() {
1515
vpn = MockVPNService()
16-
sut = VPNState<MockVPNService, MockSession>()
17-
session = MockSession()
18-
session.hasSession = true
19-
view = sut.environmentObject(vpn).environmentObject(session)
16+
sut = VPNState<MockVPNService>()
17+
state = AppState(persistent: false)
18+
state.login(baseAccessURL: URL(string: "https://coder.example.com")!, sessionToken: "fake-token")
19+
view = sut.environmentObject(vpn).environmentObject(state)
2020
}
2121

2222
@Test

0 commit comments

Comments
 (0)
Please sign in to comment.