mirror of
https://github.com/kfattic/cupsadmin.git
synced 2026-11-13 19:06:43 -05:00
Judge the delete by the read-back (is the queue gone?) instead of lpadmin's exit status. Deleting a queue that was removed elsewhere before the sidebar refreshed made lpadmin fail with 'The printer or class does not exist', and the app showed a failure alert although the queue was gone. Now the status bar shows 'Deleted <queue>' and os_log notes it was already deleted. Unit tests for all four outcomes, plus an opt-in live test (CUPSKIT_LIVE_DELETE=1) on a name that never existed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
102 lines
5.1 KiB
Swift
102 lines
5.1 KiB
Swift
@testable import CupsKit
|
|
import Foundation
|
|
import Testing
|
|
|
|
/// Opt-in: CUPSKIT_LIVE_JOBS=<queueA>,<queueB> ./test.sh — two throwaway, paused queues.
|
|
/// Submits /etc/hosts as jobs, drives every action, and leaves both queues paused with no active jobs.
|
|
@Suite(.enabled(if: ProcessInfo.processInfo.environment["CUPSKIT_LIVE_JOBS"] != nil), .serialized)
|
|
struct LiveQueueActions {
|
|
let queues = (ProcessInfo.processInfo.environment["CUPSKIT_LIVE_JOBS"] ?? "").split(separator: ",").map(String.init)
|
|
|
|
private func submit(_ queue: String) throws -> Int {
|
|
let result = try CupsTools.run(CupsTools.lpPath, ["-d", queue, "-t", "cupskit live test", "/etc/hosts"])
|
|
let text = String(decoding: result.standardOutput, as: UTF8.self) // "request id is Q-123 (1 file(s))"
|
|
guard result.succeeded, let id = text.split(separator: " ").compactMap({ $0.split(separator: "-").last.flatMap { Int($0) } }).first else {
|
|
throw CupsAdminError.failed("lp failed: \(result.message)")
|
|
}
|
|
return id
|
|
}
|
|
|
|
@Test func jobHoldReleaseMoveCancel() async throws {
|
|
try #require(queues.count == 2)
|
|
let client = CupsClient()
|
|
let id = try submit(queues[0])
|
|
|
|
let held = await QueueActions.perform(.hold, jobIDs: [id], client: client)[id]
|
|
#expect(held?.succeeded == true, "\(String(describing: held))")
|
|
#expect(held?.command == "/usr/bin/lp -i \(id) -H hold")
|
|
|
|
let released = await QueueActions.perform(.release, jobIDs: [id], client: client)[id]
|
|
#expect(released?.succeeded == true, "\(String(describing: released))")
|
|
|
|
let moved = await QueueActions.perform(.move(to: queues[1]), jobIDs: [id], client: client)[id]
|
|
#expect(moved?.succeeded == true, "\(String(describing: moved))")
|
|
|
|
let canceled = await QueueActions.perform(.cancel, jobIDs: [id], client: client)[id]
|
|
#expect(canceled?.succeeded == true, "\(String(describing: canceled))")
|
|
|
|
// Releasing an already-canceled job must fail, not report success.
|
|
let bogus = await QueueActions.perform(.hold, jobIDs: [id], client: client)[id]
|
|
#expect(bogus?.succeeded == false)
|
|
}
|
|
|
|
@Test func cancelAllPauseResumeAcceptReject() async throws {
|
|
try #require(queues.count == 2)
|
|
let client = CupsClient()
|
|
let queue = queues[1]
|
|
_ = try submit(queue)
|
|
_ = try submit(queue)
|
|
|
|
let cancelAll = try await QueueActions.perform(.cancelAllJobs, queue: queue, client: client)
|
|
#expect(cancelAll.succeeded, "\(cancelAll)")
|
|
#expect(try await client.getJobs(queue: queue, which: "not-completed", requested: ["job-id"]).isEmpty)
|
|
|
|
for action in [PrinterAction.reject, .accept, .resume, .pause] {
|
|
let outcome = try await QueueActions.perform(action, queue: queue, client: client)
|
|
#expect(outcome.succeeded, "\(action): \(outcome)")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Delete is judged by whether the queue is gone afterwards, not by lpadmin's exit status.
|
|
@Suite struct DeleteReadBack {
|
|
private func result(_ status: Int32, _ stderr: String = "") -> ToolResult {
|
|
ToolResult(executable: CupsTools.lpadminPath, arguments: ["-x", "Old_Queue"], status: status,
|
|
standardOutput: Data(), standardError: Data(stderr.utf8))
|
|
}
|
|
|
|
@Test func deletedNormally() {
|
|
let outcome = QueueActions.deleteOutcome(queue: "Old_Queue", result: result(0), stillExists: false)
|
|
#expect(outcome == ActionOutcome(succeeded: true, command: "/usr/sbin/lpadmin -x Old_Queue", message: nil))
|
|
}
|
|
|
|
/// The reported bug: the queue was removed elsewhere before Delete Printer ran, lpadmin said
|
|
/// "The printer or class does not exist", and the app showed a failure alert.
|
|
@Test func alreadyGoneIsASuccess() {
|
|
let outcome = QueueActions.deleteOutcome(queue: "Old_Queue",
|
|
result: result(1, "lpadmin: The printer or class does not exist."),
|
|
stillExists: false)
|
|
#expect(outcome.succeeded)
|
|
#expect(outcome.message == "Old_Queue was already deleted")
|
|
}
|
|
|
|
@Test func failureWhileQueueRemainsShowsToolOutput() {
|
|
let outcome = QueueActions.deleteOutcome(queue: "Old_Queue", result: result(1, "lpadmin: Forbidden"), stillExists: true)
|
|
#expect(!outcome.succeeded && outcome.message == "lpadmin: Forbidden")
|
|
}
|
|
|
|
@Test func exitZeroButStillThereIsNotApplied() {
|
|
let outcome = QueueActions.deleteOutcome(queue: "Old_Queue", result: result(0), stillExists: true)
|
|
#expect(!outcome.succeeded && outcome.message == "Not applied: Old_Queue still exists")
|
|
}
|
|
|
|
/// Opt-in (CUPSKIT_LIVE_DELETE=1): the real lpadmin and cupsd on a name that never existed. Writes nothing.
|
|
@Test(.enabled(if: ProcessInfo.processInfo.environment["CUPSKIT_LIVE_DELETE"] != nil))
|
|
func liveDeleteOfMissingQueue() async throws {
|
|
let name = "cupsadmin_missing_\(UInt32.random(in: 0 ... .max))"
|
|
let outcome = try await QueueActions.delete(queue: name, client: CupsClient())
|
|
#expect(outcome.succeeded, "\(outcome)")
|
|
#expect(outcome.message == "\(name) was already deleted")
|
|
}
|
|
}
|