johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
// Decodes the XRPC answers the screenshots recorded with the app's own models,
// and fails naming the first field each can't read. Run by ios/check-models.sh.
import Foundation
func check<T: Decodable>(_ type: T.Type, _ data: Data) throws { _ = try JSONDecoder().decode(type, from: data) }
let readers: [String: (Data) throws -> Void] = [
"describeServer": { try check(ServerDescription.self, $0) },
"getThread": { try check(PullThread.self, $0) },
"getRepo": { try check(RepoDetail.self, $0) },
"getTree": { try check(Tree.self, $0) },
"getFile": { try check(FileAnswer.self, $0) },
"listItems": { try check(ItemList.self, $0) },
"getActorRepos": { try check(RepoList.self, $0) },
"getProfile": { try check(Profile.self, $0) },
"listBlocks": { try check(BlockList.self, $0) },
"getAccountData": { try check(AccountData.self, $0) },
"getFeed": { try check(Feed.self, $0) },
"listNotifications": { try check(NotificationList.self, $0) },
"listBranches": { try check(BranchList.self, $0) },
"listCommits": { try check(CommitList.self, $0) },
"getCommit": { try check(CommitAnswer.self, $0) },
"listReleases": { try check(ReleaseList.self, $0) },
"listRuns": { try check(RunList.self, $0) },
"getCheck": { try check(RunDetail.self, $0) },
]
let path = CommandLine.arguments[1]
let fixtures = try JSONSerialization.jsonObject(with: Data(contentsOf: URL(fileURLWithPath: path))) as! [String: Any]
let answers = fixtures["answers"] as! [String: Any]
var read = 0
var failed = 0
for (url, answer) in answers.sorted(by: { $0.key < $1.key }) {
guard url.hasPrefix("/xrpc/dev.knotless."),
let name = url.dropFirst("/xrpc/dev.knotless.".count).split(separator: "?").first,
let reader = readers[String(name)],
let recorded = answer as? [String: Any],
recorded["status"] as? Int == 200,
let body = recorded["body"] as? String
else { continue }
do {
try reader(Data(body.utf8))
read += 1
} catch {
print("\(url)\n \(error)")
failed += 1
}
}
print("\(read) answers read, \(failed) not")
if failed > 0 || read == 0 { exit(1) }
// What the merge section says of each state: nothing from the command line, and
// the queue offered only to a pull it would take.
func status(_ state: String, checks: String = "none", reviews: String? = nil, queueOnly: Bool = false) -> MergeStatus {
MergeStatus(state: state, checks: checks, queueOnly: queueOnly, reviews: reviews, may: .init(merge: true, queue: true))
}
let saying: [(MergeStatus, String, Bool)] = [
(status("clean", checks: "passed"), "Ready to merge", true),
(status("clean", checks: "passed", queueOnly: true), "Ready for the merge queue", true),
(status("conflicting"), "Conflicts with main", false),
(status("draft"), "Still a draft", false),
(status("blocked", checks: "failed"), "Checks failed", false),
(status("blocked", checks: "running"), "Checks are running", true),
(status("blocked", checks: "pending"), "Not checked yet", true),
(status("blocked", checks: "passed", reviews: "blocked"), "Review required", false),
]
var wrong = 0
func expect(_ held: Bool, _ what: String) {
if !held {
print(what)
wrong += 1
}
}
for (s, title, queues) in saying {
let said = Standing.of(s, base: "main")
expect(said.title == title, "\(s.state)/\(s.checks ?? ""): says \(said.title), not \(title)")
expect(Standing.canQueue(s) == queues, "\(title): the queue \(queues ? "isn't" : "is") offered")
}
let ejected = Standing.ejected(reason: "its batch failed its checks at 6f89484c63a3, on main at 226488be4afa: test (failure). Their logs: `knotless run list knotless://a/b --pull at://c --log`")
expect(ejected.lines == ["Its batch failed its checks: test (failure)"], "ejected says \(ejected.lines)")
let unreviewed = Standing.ejected(reason: "it is not reviewed as main asks: reviews 0 of 1. It waited 30m for that review. Anyone but its author who may write approves it with `knotless comment add knotless://a/b c <text> --approve`; the owner, its author or not, merges past it with `--force`")
expect(unreviewed.lines == ["It is not reviewed as main asks: reviews 0 of 1. It waited 30m for that review"], "ejected says \(unreviewed.lines)")
for said in saying.map({ Standing.of($0.0, base: "main") }) + [ejected, unreviewed] {
for line in said.lines where line.contains("knotless ") || line.contains("at://") || line.contains("--") {
expect(false, "reads like the command line: \(line)")
}
}
// The patterns and hues of web/src/lib/format.ts, and where a picture is found.
func cells(_ seed: String) -> [[Int]] { Identicon.cells(seed).map { [$0.x, $0.y] } }
let person = Person(did: "did:plc:a", handle: "a.test")
func said(_ uri: String, reply: String? = nil) -> Comment { Comment(uri: uri, author: person, createdAt: "t", reply: reply) }
expect(Comment.threaded([said("a"), said("b"), said("c", reply: "a"), said("d", reply: "gone")]).compactMap(\.uri) == ["a", "c", "b", "d"], "a thread's answers aren't under it")
expect(RecentSearches.decode(RecentSearches.adding("b", to: "a\nb\nc")) == ["b", "a", "c"], "a repeated search isn't moved up")
let fences = MarkdownFences.ranges("<p>a</p><pre><code data-ranges=\"keyword:0,2\">if a < b && c {\n}\n</code></pre><pre><code>plain\n</code></pre>")
expect(fences == ["if a < b && c {\n}": "keyword:0,2"], "a fence's colours weren't found")
expect(Identicon.hue("did:plc:abe24xewga4qdbzjcxmuzdt3") == 276, "a hue differs from the pages'")
expect(cells("did:plc:abe24xewga4qdbzjcxmuzdt3") == [[0,1],[4,1],[0,3],[4,3],[0,4],[4,4],[1,0],[3,0],[1,2],[3,2],[2,2],[2,4]], "a pattern differs from the pages'")
expect(Identicon.hue("Claude Opus 5.5") == 358, "a hue differs from the pages'")
expect(cells("Claude Opus 5.5") == [[0,3],[4,3],[1,0],[3,0],[1,1],[3,1],[1,3],[3,3],[2,0],[2,2]], "a pattern differs from the pages'")
let place = MarkdownPlace(repo: RepoRef(did: "did:plc:o", handle: "o.test", name: "r"), ref: "main")
expect(place.pictures("../assets/logo.svg?v=1").first?.absoluteString == "https://knotless.dev/o.test/r/raw/main/assets/logo.svg?v=1", "a relative picture: \(place.pictures("../assets/logo.svg?v=1"))")
expect(place.pictures("javascript:alert(1)").isEmpty, "only http and https pictures load")
let blob = "https://pds.test/xrpc/com.atproto.sync.getBlob?did=did:plc:a&cid=bafa"
let said = MarkdownPlace(repo: RepoRef(did: "did:plc:o", handle: "o.test", name: "r"), subject: "at://did:plc:a/dev.knotless.comment/1")
expect(said.pictures(blob).first?.path == "/xrpc/dev.knotless.getAttachment" && said.pictures(blob).count == 2, "an attached picture: \(said.pictures(blob))")
expect(place.pictures(blob).map(\.absoluteString) == [blob], "another's picture stays where it is")
// The notifications among preferences of other kinds, which it passes over.
let answered = #"{"preferences":[{"$type":"dev.knotless.getPreferences#appearancePref","theme":"dark"},{"$type":"dev.knotless.getPreferences#notificationPref","reasons":[{"reason":"star","enabled":false}],"followedOnly":true},{"$type":"dev.knotless.later"}]}"#
let heard = try JSONDecoder().decode(Preferences.self, from: Data(answered.utf8)).notifications
expect(heard == NotificationChoices(reasons: [.init(reason: "star", enabled: false)], followedOnly: true), "the notifications weren't found among the preferences: \(String(describing: heard))")
var table = MarkdownTable(header: "| a | b | c |", dashes: "|:--|--:|:-:|")
expect(table?.header == ["a", "b", "c"] && table?.alignments == [.leading, .trailing, .center], "a table's head: \(String(describing: table))")
table?.add("| one \\| two | `x` |")
table?.add("| 1 | 2 | 3 | 4 |")
expect(table?.rows == [["one | two", "`x`", ""], ["1", "2", "3"]], "a table's rows: \(String(describing: table?.rows))")
expect(MarkdownTable(header: "a | b", dashes: "--- | ---") != nil, "a table with no pipes at its ends")
expect(MarkdownTable(header: "| a | b |", dashes: "| --- |") == nil, "a table whose columns don't match")
expect(MarkdownTable(header: "a | b", dashes: "not | dashes") == nil, "words as a table's dashes")
expect(MarkdownTable(header: "just words", dashes: "---") == nil, "a rule as a table's dashes")
// The tasks web/src/lib/tasks.ts finds, each ticked on its own line.
let written = "- [ ] one\n* [x] two\n3. [X] three\n4) [ ] four\n> - [ ] quoted\n - [ ] under\n-[ ] no\n- [ ]no\n- [y] no\n```\n- [ ] fenced\n```\n~~~\n- [x] fenced\n~~~\n- [ ]"
expect(MarkdownTasks.all(written).map(\.done) == [false, true, true, false, false, false, false], "the tasks found: \(MarkdownTasks.all(written))")
expect(MarkdownTasks.ticked("- [ ] a\n```\n- [ ] b\n```\n- [ ] c", 1, done: true) == "- [ ] a\n```\n- [ ] b\n```\n- [x] c", "a tick rewrote another line")
expect(MarkdownTasks.ticked("é\n- [x] 🦊 a\r\n", 0, done: false) == "é\n- [ ] 🦊 a\r\n", "an untick changed more than its mark")
expect(MarkdownTasks.ticked("- [ ] a", 1, done: true) == nil, "a tick of no task")
// The folds the web pages draw, `<details>` to `</details>`.
let folded = "a\n<details open>\n<SUMMARY> Claude </summary>\n\n```\n</details>\n```\n<details><summary>in</summary></details>\n<details>\n</details>\n</Details>\nb".components(separatedBy: "\n")
expect(MarkdownFolds.fold(folded, at: 0, end: folded.count) == nil, "a line opening no fold")
expect(MarkdownFolds.fold(folded, at: 1, end: folded.count) == .init(summary: "Claude", open: true, inside: 3..<10, next: 11), "a fold with fenced code and folds inside: \(String(describing: MarkdownFolds.fold(folded, at: 1, end: folded.count)))")
expect(MarkdownFolds.fold(folded, at: 7, end: folded.count) == .init(summary: "in", open: false, inside: 8..<8, next: 8), "a fold on one line")
expect(MarkdownFolds.fold(folded, at: 8, end: 9) == .init(summary: "Details", open: false, inside: 9..<9, next: 9), "a fold left open, with no summary")
expect(MarkdownFolds.fold(["<details>words"], at: 0, end: 1) == nil, "words beside a fold's tag")
if wrong > 0 { exit(1) }
// Every write the app makes, for check-writes.mjs to hold to its lexicon.
let repo = RepoRef(did: "did:plc:bcgltzqazw5tb6k2g3ttenbj", handle: "alice.test", name: "tally")
let pull = "at://did:plc:bcgltzqazw5tb6k2g3ttenbj/dev.knotless.pull/3mzlqso3vpyqg"
let choices = NotificationChoices(reasons: [.init(reason: "star", enabled: false), .init(reason: "mention", enabled: true)], followedOnly: true)
let writes: [Write] = [
Writes.comment(repo: repo, subject: pull, body: "Looks right", verdict: nil),
Writes.comment(repo: repo, subject: pull, body: "", verdict: "approve"),
Writes.comment(repo: repo, subject: pull, body: "Not yet", verdict: "changes"),
Writes.issue(repo: repo, title: "It breaks", body: ""),
Writes.issue(repo: repo, title: "It breaks", body: "On empty files"),
Writes.react(repo: repo, subject: pull, content: "+1", undo: false),
Writes.react(repo: repo, subject: pull, content: "+1", undo: true),
Writes.setState(repo: repo, subject: pull, state: "closed"),
Writes.setState(repo: repo, subject: pull, state: "open"),
Writes.merge(repo: repo, pull: pull),
Writes.queue(repo: repo, pull: pull),
Writes.leaveQueue(repo: repo, pull: pull),
Writes.star(repo: repo, undo: false),
Writes.star(repo: repo, undo: true),
Writes.fork(repo: repo),
Writes.hear(repo: repo, level: "all"),
Writes.hear(repo: repo, level: "releases"),
Writes.hear(repo: repo, level: "ignore"),
Writes.hear(repo: repo, level: "participating"),
Writes.follow(did: repo.did, undo: false),
Writes.follow(did: repo.did, undo: true),
Writes.rerun(repo: repo, pull: pull, ref: nil, workflow: nil),
Writes.rerun(repo: repo, pull: pull, ref: nil, workflow: "test"),
Writes.rerun(repo: repo, pull: nil, ref: "refs/heads/main", workflow: "deploy"),
Writes.seen(at: "2026-11-05T12:00:00.000Z"),
Writes.edit(repo: repo, subject: pull, body: "- [x] Done"),
Writes.registerPush(token: "0a1b", environment: "sandbox"),
Writes.unregisterPush(token: "0a1b"),
Writes.report(subject: repo.did, reason: "spam", text: ""),
Writes.report(subject: pull, reason: "rude", text: "Unkind to a new contributor"),
Writes.block(subject: repo.did, undo: false),
Writes.block(subject: repo.did, undo: true),
Writes.block(subject: "bob.test", repo: repo.uri, undo: false),
Writes.block(subject: repo.did, repo: repo.uri, undo: true),
Writes.deleteData(did: repo.did, limit: 100),
Writes.notifications(choices),
]
let made = writes.map { ["method": $0.method, "input": $0.body] as [String: Any] }
try JSONSerialization.data(withJSONObject: made).write(to: URL(fileURLWithPath: CommandLine.arguments[2]))