feat(ios): add native app foundation and unsigned ipa build
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import XCTest
|
||||
|
||||
@testable import MultiSimAdmin
|
||||
|
||||
@MainActor
|
||||
final class FleetViewModelTests: XCTestCase {
|
||||
func testRefreshSortsByNameAndReportsStatus() async throws {
|
||||
let repository = MemoryInstanceRepository()
|
||||
let model = FleetViewModel(
|
||||
repository: repository,
|
||||
probe: ConnectionProbe(client: AlwaysOnlineClient()),
|
||||
credentials: NoopCredentialStore()
|
||||
)
|
||||
try await model.add(InstanceInput(
|
||||
name: "Zulu",
|
||||
origin: URL(string: "http://192.168.1.30:3000")!,
|
||||
tags: ["lab"],
|
||||
password: nil
|
||||
))
|
||||
try await model.add(InstanceInput(
|
||||
name: "Alpha",
|
||||
origin: URL(string: "http://192.168.1.20:3000")!,
|
||||
tags: [],
|
||||
password: nil
|
||||
))
|
||||
|
||||
await model.refresh()
|
||||
|
||||
XCTAssertEqual(model.rows.map(\.instance.name), ["Alpha", "Zulu"])
|
||||
XCTAssertEqual(model.rows.map(\.status), [.online, .online])
|
||||
}
|
||||
|
||||
func testAddValidatesPasswordAndDeleteRemovesCredential() async throws {
|
||||
let repository = MemoryInstanceRepository()
|
||||
let credentials = NoopCredentialStore()
|
||||
let model = FleetViewModel(
|
||||
repository: repository,
|
||||
probe: ConnectionProbe(client: AlwaysOnlineClient()),
|
||||
credentials: credentials
|
||||
)
|
||||
try await model.add(InstanceInput(
|
||||
name: "Secret",
|
||||
origin: URL(string: "http://192.168.1.40:3000")!,
|
||||
password: "pass"
|
||||
))
|
||||
|
||||
let row = try XCTUnwrap(model.rows.first)
|
||||
XCTAssertTrue(row.instance.credentialConfigured)
|
||||
XCTAssertEqual(credentials.saved[row.instance.id], "pass")
|
||||
|
||||
await model.delete([row.instance.id])
|
||||
XCTAssertTrue(model.rows.isEmpty)
|
||||
XCTAssertNil(credentials.saved[row.instance.id])
|
||||
}
|
||||
}
|
||||
|
||||
struct AlwaysOnlineClient: SimAdminClientProtocol {
|
||||
func send(_: UpstreamRoute, baseURL: URL, cookie: String?) async throws -> UpstreamResponse {
|
||||
UpstreamResponse(status: 200, data: Data("{}".utf8))
|
||||
}
|
||||
}
|
||||
|
||||
final class NoopCredentialStore: CredentialStore {
|
||||
private let lock = NSLock()
|
||||
private var savedStorage: [UUID: String] = [:]
|
||||
private var removedStorage: [UUID] = []
|
||||
|
||||
var saved: [UUID: String] {
|
||||
lock.withLock { savedStorage }
|
||||
}
|
||||
|
||||
var removed: [UUID] {
|
||||
lock.withLock { removedStorage }
|
||||
}
|
||||
|
||||
func get(instanceID: UUID) throws -> String? {
|
||||
saved[instanceID]
|
||||
}
|
||||
|
||||
func set(instanceID: UUID, password: String) throws {
|
||||
lock.withLock { savedStorage[instanceID] = password }
|
||||
}
|
||||
|
||||
func remove(instanceID: UUID) throws {
|
||||
lock.withLock {
|
||||
savedStorage[instanceID] = nil
|
||||
removedStorage.append(instanceID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class MemoryInstanceRepository: InstanceRepository {
|
||||
private let lock = NSLock()
|
||||
private var itemsStorage: [Instance] = []
|
||||
|
||||
private var items: [Instance] {
|
||||
get { lock.withLock { itemsStorage } }
|
||||
set { lock.withLock { itemsStorage = newValue } }
|
||||
}
|
||||
|
||||
func list(_: InstanceQuery) async throws -> [Instance] {
|
||||
items.sorted { $0.name.localizedCompare($1.name) == .orderedAscending }
|
||||
}
|
||||
|
||||
func get(_ id: UUID) async throws -> Instance? {
|
||||
items.first { $0.id == id }
|
||||
}
|
||||
|
||||
func create(_ input: InstanceInput) async throws -> Instance {
|
||||
let now = Date()
|
||||
let instance = Instance(
|
||||
id: UUID(),
|
||||
name: input.name,
|
||||
origin: input.origin,
|
||||
tags: input.tags,
|
||||
groupId: input.groupId,
|
||||
credentialConfigured: !(input.password ?? "").isEmpty,
|
||||
configRevision: 1,
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
)
|
||||
items.append(instance)
|
||||
return instance
|
||||
}
|
||||
|
||||
func update(_: UUID, expectedRevision: Int, _: InstancePatch) async throws -> Instance {
|
||||
throw RepositoryError.notFound
|
||||
}
|
||||
|
||||
func delete(_ id: UUID, expectedRevision: Int) async throws {
|
||||
items.removeAll { $0.id == id }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user