Files
lanework/KanbanTests/DittoZipArchiverTests.swift
T
rzen da5d310673 File ▸ Share… stages a board as a zip and hands it to the system share sheet
Duplicate's own posture — a faithful copy, `.git` the sole exclusion, attachments/comments/trash
carried verbatim — staged to a temp directory (BoardShareStager, ditto-zipped via
DittoZipArchiver) and presented through NSSharingServicePicker (BoardSharePresentation), anchored
to the board window's toolbar or its center. Follows Duplicate/Save as Template's flush-then-
cancellable-copy sequence under the banner's in-progress row (ShareBoardCommand, AppCommands.swift),
menu-validated on focus alone rather than the read-only lock (a share is a read, Print's own
posture) with the one carve-out an open inline title editor still needs. WriteOperation gains
.shareBoard for the banner vocabulary; 11-command-nexus.md's File menu table gains the row.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
2026-08-09 02:13:54 -04:00

56 lines
2.6 KiB
Swift

import Foundation
import Testing
@testable import Kanban
/// **The sandbox verification the card's design ruling asks for**: "prefer … `Process`+`/usr/bin/
/// ditto` … VERIFY in a sandboxed build".
///
/// `KanbanTests` is host-application-hosted (`TEST_HOST` in `project.yml` points at the built
/// `Lanework.app`), so this suite runs *inside* the real, sandboxed app process —
/// `Kanban.entitlements`'s `com.apple.security.app-sandbox` and all — rather than in a bare XCTest
/// bundle with none of the app's confinement. A green run here is empirical: `/usr/bin/ditto`,
/// spawned as this app's child, can read a folder this process owns and write a `.zip` beside it,
/// under the same entitlements a real File ▸ Share… run would have. That is the closest this suite
/// can get to the real button without driving AppKit (`BoardSharePresentation`'s own boundary).
@Suite("DittoZipArchiver")
struct DittoZipArchiverTests {
@Test("A folder zips inside the sandbox, and the result is a real zip")
func zipsInsideTheSandbox() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
let source = fixture.url("Payload")
try FileManager.default.createDirectory(at: source, withIntermediateDirectories: true)
try Data("hello from the sandbox\n".utf8).write(to: source.appendingPathComponent("note.txt"))
try FileManager.default.createDirectory(
at: source.appendingPathComponent("nested", isDirectory: true),
withIntermediateDirectories: true
)
try Data([0x01, 0x02, 0x03]).write(to: source.appendingPathComponent("nested/data.bin"))
let destination = fixture.url("Payload.zip")
try DittoZipArchiver.zip(contentsOf: source, to: destination)
#expect(FileManager.default.fileExists(atPath: destination.path))
let bytes = try Data(contentsOf: destination)
#expect(!bytes.isEmpty)
// The local file header signature `PK\x03\x04` — proof this is an actual zip archive and
// not, say, an empty file `ditto` merely touched.
#expect(bytes.prefix(4) == Data([0x50, 0x4B, 0x03, 0x04]))
}
@Test("A source that doesn't exist is a failure, not a silent empty archive")
func missingSourceFails() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
let missing = fixture.url("Never")
let destination = fixture.url("Never.zip")
#expect(throws: DittoZipArchiver.Failure.self) {
try DittoZipArchiver.zip(contentsOf: missing, to: destination)
}
#expect(!FileManager.default.fileExists(atPath: destination.path))
}
}