Import Coucou 0.1.9 (Louis-CFM/coucou@83708fe), Mac app only
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,355 @@
|
||||
import Foundation
|
||||
|
||||
@main
|
||||
enum GitHubPulseTests {
|
||||
|
||||
static var failures = 0
|
||||
|
||||
static func check(_ label: String, _ got: Bool) {
|
||||
if got { print(" ✓ \(label)") }
|
||||
else { print(" ✗ \(label)"); failures += 1 }
|
||||
}
|
||||
|
||||
// MARK: - Fixture JSON
|
||||
|
||||
// Valid response: PR #42 SUCCESS/APPROVED, PR #43 null CI (unknown)/null review,
|
||||
// repo "testuser/myrepo" PENDING, archived repo filtered out,
|
||||
// one to-review PR from another repo.
|
||||
static let validJSON: Data = """
|
||||
{
|
||||
"data": {
|
||||
"viewer": {
|
||||
"login": "testuser",
|
||||
"pullRequests": {
|
||||
"nodes": [
|
||||
{
|
||||
"number": 42,
|
||||
"title": "Add feature",
|
||||
"url": "https://github.com/testuser/myrepo/pull/42",
|
||||
"isDraft": false,
|
||||
"reviewDecision": "APPROVED",
|
||||
"repository": {"nameWithOwner": "testuser/myrepo", "url": "https://github.com/testuser/myrepo"},
|
||||
"commits": {"nodes": [{"commit": {"statusCheckRollup": {"state": "SUCCESS"}}}]}
|
||||
},
|
||||
{
|
||||
"number": 43,
|
||||
"title": "Fix bug",
|
||||
"url": "https://github.com/testuser/myrepo/pull/43",
|
||||
"isDraft": true,
|
||||
"reviewDecision": null,
|
||||
"repository": {"nameWithOwner": "testuser/myrepo", "url": "https://github.com/testuser/myrepo"},
|
||||
"commits": {"nodes": [{"commit": {"statusCheckRollup": null}}]}
|
||||
}
|
||||
]
|
||||
},
|
||||
"repositories": {
|
||||
"nodes": [
|
||||
{
|
||||
"nameWithOwner": "testuser/myrepo",
|
||||
"url": "https://github.com/testuser/myrepo",
|
||||
"isArchived": false,
|
||||
"defaultBranchRef": {
|
||||
"name": "main",
|
||||
"target": {"statusCheckRollup": {"state": "PENDING"}}
|
||||
}
|
||||
},
|
||||
{
|
||||
"nameWithOwner": "testuser/archived",
|
||||
"url": "https://github.com/testuser/archived",
|
||||
"isArchived": true,
|
||||
"defaultBranchRef": {
|
||||
"name": "main",
|
||||
"target": {"statusCheckRollup": {"state": "SUCCESS"}}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"reviewRequested": {
|
||||
"issueCount": 1,
|
||||
"nodes": [
|
||||
{
|
||||
"number": 7,
|
||||
"title": "Review this",
|
||||
"url": "https://github.com/other/repo/pull/7",
|
||||
"isDraft": false,
|
||||
"author": {"login": "otheruser"},
|
||||
"repository": {"nameWithOwner": "other/repo", "url": "https://github.com/other/repo"}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
static let emptyJSON: Data = """
|
||||
{
|
||||
"data": {
|
||||
"viewer": {
|
||||
"login": "testuser",
|
||||
"pullRequests": {"nodes": []},
|
||||
"repositories": {"nodes": []}
|
||||
},
|
||||
"reviewRequested": {"issueCount": 0, "nodes": []}
|
||||
}
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
// MARK: - Main
|
||||
|
||||
static func main() {
|
||||
|
||||
// ── GitHubPulse.parse ─────────────────────────────────────────────────
|
||||
print("GitHubPulse.parse")
|
||||
do {
|
||||
let pulse = GitHubPulse.parse(validJSON)
|
||||
check("parse returns non-nil", pulse != nil)
|
||||
check("login", pulse?.login == "testuser")
|
||||
check("2 myPRs", pulse?.myPRs.count == 2)
|
||||
check("PR#42 id", pulse?.myPRs[0].id == "testuser/myrepo#42")
|
||||
check("PR#42 ci success", pulse?.myPRs[0].ci == .success)
|
||||
check("PR#42 review approved", pulse?.myPRs[0].review == .approved)
|
||||
check("PR#43 ci unknown (null rollup)",pulse?.myPRs[1].ci == .unknown)
|
||||
check("PR#43 review unknown (null)", pulse?.myPRs[1].review == .unknown)
|
||||
check("PR#43 isDraft", pulse?.myPRs[1].isDraft == true)
|
||||
check("1 mainCI (archived filtered)", pulse?.mainCI.count == 1)
|
||||
check("mainCI repo", pulse?.mainCI[0].repo == "testuser/myrepo")
|
||||
check("mainCI ci pending", pulse?.mainCI[0].ci == .pending)
|
||||
check("mainCI branch main", pulse?.mainCI[0].branch == "main")
|
||||
check("1 toReview", pulse?.toReview.count == 1)
|
||||
check("toReview id", pulse?.toReview[0].id == "other/repo#7")
|
||||
check("hasPending (mainCI pending)", pulse?.hasPending == true)
|
||||
}
|
||||
|
||||
// ── GitHubPulse.parse — empty ─────────────────────────────────────────
|
||||
print("GitHubPulse.parse — empty")
|
||||
do {
|
||||
let pulse = GitHubPulse.parse(emptyJSON)
|
||||
check("empty parses ok", pulse != nil)
|
||||
check("no myPRs", pulse?.myPRs.isEmpty == true)
|
||||
check("no mainCI", pulse?.mainCI.isEmpty == true)
|
||||
check("no toReview", pulse?.toReview.isEmpty == true)
|
||||
check("!hasPending", pulse?.hasPending == false)
|
||||
}
|
||||
|
||||
// ── GitHubPulse.parse — bad data ──────────────────────────────────────
|
||||
print("GitHubPulse.parse — bad data")
|
||||
do {
|
||||
check("garbage → nil", GitHubPulse.parse("garbage".data(using: .utf8)!) == nil)
|
||||
check("empty obj → nil", GitHubPulse.parse("{}".data(using: .utf8)!) == nil)
|
||||
}
|
||||
|
||||
// ── CIState init ──────────────────────────────────────────────────────
|
||||
print("CIState init")
|
||||
do {
|
||||
check("nil → unknown", CIState(rawGitHub: nil) == .unknown)
|
||||
check("PENDING → pending", CIState(rawGitHub: "PENDING") == .pending)
|
||||
check("EXPECTED → pending", CIState(rawGitHub: "EXPECTED") == .pending)
|
||||
check("SUCCESS → success", CIState(rawGitHub: "SUCCESS") == .success)
|
||||
check("FAILURE → failure", CIState(rawGitHub: "FAILURE") == .failure)
|
||||
check("ERROR → failure", CIState(rawGitHub: "ERROR") == .failure)
|
||||
check("other → unknown", CIState(rawGitHub: "WAITING") == .unknown)
|
||||
}
|
||||
|
||||
// ── GitHubPulse.events — first poll silent ────────────────────────────
|
||||
print("GitHubPulse.events — first poll silent")
|
||||
do {
|
||||
let pulse = GitHubPulse.parse(validJSON)!
|
||||
let events = GitHubPulse.events(old: nil, new: pulse)
|
||||
check("first poll → no events", events.isEmpty)
|
||||
}
|
||||
|
||||
// ── GitHubPulse.events — pending → success ────────────────────────────
|
||||
print("GitHubPulse.events — pending → success")
|
||||
do {
|
||||
var oldPulse = GitHubPulse.parse(emptyJSON)!
|
||||
oldPulse.myPRs = [GitHubPR(id: "r/p#1", title: "T", url: "", repo: "r/p",
|
||||
number: 1, isDraft: false, ci: .pending, review: .unknown)]
|
||||
var newPulse = oldPulse
|
||||
newPulse.myPRs[0].ci = .success
|
||||
let events = GitHubPulse.events(old: oldPulse, new: newPulse)
|
||||
check("pending→success → ciPassed", events == [.ciPassed(prId: "r/p#1")])
|
||||
}
|
||||
|
||||
// ── GitHubPulse.events — success → failure ────────────────────────────
|
||||
print("GitHubPulse.events — success → failure")
|
||||
do {
|
||||
var oldPulse = GitHubPulse.parse(emptyJSON)!
|
||||
oldPulse.myPRs = [GitHubPR(id: "r/p#2", title: "T", url: "", repo: "r/p",
|
||||
number: 2, isDraft: false, ci: .success, review: .unknown)]
|
||||
var newPulse = oldPulse
|
||||
newPulse.myPRs[0].ci = .failure
|
||||
let events = GitHubPulse.events(old: oldPulse, new: newPulse)
|
||||
check("success→failure → ciFailed", events == [.ciFailed(prId: "r/p#2")])
|
||||
}
|
||||
|
||||
// ── GitHubPulse.events — main CI failure ──────────────────────────────
|
||||
print("GitHubPulse.events — main CI failure")
|
||||
do {
|
||||
var oldPulse = GitHubPulse.parse(emptyJSON)!
|
||||
oldPulse.mainCI = [GitHubRepoCI(repo: "a/b", url: "", branch: "main", ci: .success)]
|
||||
var newPulse = oldPulse
|
||||
newPulse.mainCI[0].ci = .failure
|
||||
let events = GitHubPulse.events(old: oldPulse, new: newPulse)
|
||||
check("main success→failure → mainFailed", events == [.mainFailed(repo: "a/b")])
|
||||
}
|
||||
|
||||
// ── GitHubPulse.events — new review request ───────────────────────────
|
||||
print("GitHubPulse.events — new review request")
|
||||
do {
|
||||
let pr = GitHubPR(id: "o/r#7", title: "R", url: "", repo: "o/r",
|
||||
number: 7, isDraft: false, ci: .unknown, review: .pending)
|
||||
var oldPulse = GitHubPulse.parse(emptyJSON)!
|
||||
var newPulse = oldPulse
|
||||
newPulse.toReview = [pr]
|
||||
let events = GitHubPulse.events(old: oldPulse, new: newPulse)
|
||||
check("new review → reviewRequested", events == [.reviewRequested(prId: "o/r#7")])
|
||||
|
||||
// Already-known review: no new event
|
||||
oldPulse.toReview = [pr]
|
||||
newPulse.toReview = [pr]
|
||||
let noEvents = GitHubPulse.events(old: oldPulse, new: newPulse)
|
||||
check("known review → no event", noEvents.isEmpty)
|
||||
}
|
||||
|
||||
// ── GitHubPulse.parse — duplicate ids ────────────────────────────────────
|
||||
print("GitHubPulse.parse — duplicate ids")
|
||||
do {
|
||||
// JSON with PR #42 duplicated: should keep only first occurrence
|
||||
let dupJSON: Data = """
|
||||
{
|
||||
"data": {
|
||||
"viewer": {
|
||||
"login": "testuser",
|
||||
"pullRequests": {
|
||||
"nodes": [
|
||||
{
|
||||
"number": 42, "title": "Add feature",
|
||||
"url": "https://github.com/testuser/myrepo/pull/42",
|
||||
"isDraft": false, "reviewDecision": "APPROVED",
|
||||
"repository": {"nameWithOwner": "testuser/myrepo", "url": "https://github.com/testuser/myrepo"},
|
||||
"commits": {"nodes": [{"commit": {"statusCheckRollup": {"state": "SUCCESS"}}}]}
|
||||
},
|
||||
{
|
||||
"number": 42, "title": "Duplicate entry",
|
||||
"url": "https://github.com/testuser/myrepo/pull/42",
|
||||
"isDraft": true, "reviewDecision": null,
|
||||
"repository": {"nameWithOwner": "testuser/myrepo", "url": "https://github.com/testuser/myrepo"},
|
||||
"commits": {"nodes": [{"commit": {"statusCheckRollup": {"state": "FAILURE"}}}]}
|
||||
}
|
||||
]
|
||||
},
|
||||
"repositories": {"nodes": []}
|
||||
},
|
||||
"reviewRequested": {"issueCount": 0, "nodes": []}
|
||||
}
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
let pulse = GitHubPulse.parse(dupJSON)
|
||||
check("dup PR → 1 myPR", pulse?.myPRs.count == 1)
|
||||
check("dup PR → keeps first title", pulse?.myPRs[0].title == "Add feature")
|
||||
check("dup PR → keeps first ci", pulse?.myPRs[0].ci == .success)
|
||||
}
|
||||
|
||||
// ── GitHubPulse.events — duplicate ids in old pulse ───────────────────
|
||||
print("GitHubPulse.events — duplicate-safe Dictionary")
|
||||
do {
|
||||
// OLD has duplicate ids (uniquingKeysWith keeps first); NEW has one entry → fires once
|
||||
var oldPulse = GitHubPulse.parse(emptyJSON)!
|
||||
let pr1 = GitHubPR(id: "r/p#1", title: "T", url: "", repo: "r/p",
|
||||
number: 1, isDraft: false, ci: .pending, review: .unknown)
|
||||
oldPulse.myPRs = [pr1, pr1] // intentional duplicate in old
|
||||
var newPulse = GitHubPulse.parse(emptyJSON)!
|
||||
var pr1Passed = pr1; pr1Passed.ci = .success
|
||||
newPulse.myPRs = [pr1Passed] // single entry in new
|
||||
let events = GitHubPulse.events(old: oldPulse, new: newPulse)
|
||||
check("dup old ids → no crash", true)
|
||||
check("dup old ids → ciPassed fires once",
|
||||
events.filter { if case .ciPassed = $0 { return true }; return false }.count == 1)
|
||||
}
|
||||
|
||||
// ── GitHubPulse.events — headSha: new PR already green ───────────────
|
||||
print("GitHubPulse.events — headSha: new PR already green")
|
||||
do {
|
||||
let old = GitHubPulse.parse(emptyJSON)!
|
||||
var newPulse = old
|
||||
newPulse.myPRs = [GitHubPR(id: "r/p#10", title: "T", url: "", repo: "r/p",
|
||||
number: 10, isDraft: false, ci: .success, review: .unknown,
|
||||
headSha: "abc111")]
|
||||
let events = GitHubPulse.events(old: old, new: newPulse)
|
||||
check("new PR green → ciPassed", events == [.ciPassed(prId: "r/p#10")])
|
||||
}
|
||||
|
||||
// ── GitHubPulse.events — headSha: new PR pending → nothing ───────────
|
||||
print("GitHubPulse.events — headSha: new PR pending → nothing")
|
||||
do {
|
||||
let old = GitHubPulse.parse(emptyJSON)!
|
||||
var newPulse = old
|
||||
newPulse.myPRs = [GitHubPR(id: "r/p#11", title: "T", url: "", repo: "r/p",
|
||||
number: 11, isDraft: false, ci: .pending, review: .unknown,
|
||||
headSha: "abc222")]
|
||||
let events = GitHubPulse.events(old: old, new: newPulse)
|
||||
check("new PR pending → no events", events.isEmpty)
|
||||
}
|
||||
|
||||
// ── GitHubPulse.events — headSha: new commit already red ─────────────
|
||||
print("GitHubPulse.events — headSha: new commit already red → ciFailed")
|
||||
do {
|
||||
var oldPulse = GitHubPulse.parse(emptyJSON)!
|
||||
oldPulse.myPRs = [GitHubPR(id: "r/p#12", title: "T", url: "", repo: "r/p",
|
||||
number: 12, isDraft: false, ci: .success, review: .unknown,
|
||||
headSha: "sha-old")]
|
||||
var newPulse = oldPulse
|
||||
newPulse.myPRs[0].ci = .failure
|
||||
newPulse.myPRs[0].headSha = "sha-new"
|
||||
let events = GitHubPulse.events(old: oldPulse, new: newPulse)
|
||||
check("new commit red → ciFailed", events == [.ciFailed(prId: "r/p#12")])
|
||||
}
|
||||
|
||||
// ── GitHubPulse.events — headSha: same sha success→success → nothing ─
|
||||
print("GitHubPulse.events — headSha: same sha success→success → nothing")
|
||||
do {
|
||||
var oldPulse = GitHubPulse.parse(emptyJSON)!
|
||||
oldPulse.myPRs = [GitHubPR(id: "r/p#13", title: "T", url: "", repo: "r/p",
|
||||
number: 13, isDraft: false, ci: .success, review: .unknown,
|
||||
headSha: "same")]
|
||||
let newPulse = oldPulse // identical SHA and CI
|
||||
let events = GitHubPulse.events(old: oldPulse, new: newPulse)
|
||||
check("same sha success→success → no event", events.isEmpty)
|
||||
}
|
||||
|
||||
// ── GitHubPulse.events — headSha: new commit on main red → mainFailed ─
|
||||
print("GitHubPulse.events — headSha: new commit on main red → mainFailed")
|
||||
do {
|
||||
var oldPulse = GitHubPulse.parse(emptyJSON)!
|
||||
oldPulse.mainCI = [GitHubRepoCI(repo: "a/b", url: "", branch: "main",
|
||||
ci: .success, headSha: "sha-old")]
|
||||
var newPulse = oldPulse
|
||||
newPulse.mainCI[0].ci = .failure
|
||||
newPulse.mainCI[0].headSha = "sha-new"
|
||||
let events = GitHubPulse.events(old: oldPulse, new: newPulse)
|
||||
check("new commit on main red → mainFailed", events == [.mainFailed(repo: "a/b")])
|
||||
}
|
||||
|
||||
// ── GitHubPulse.isStale ───────────────────────────────────────────────
|
||||
print("GitHubPulse.isStale")
|
||||
do {
|
||||
let now = Date()
|
||||
check("nil fetchedAt → stale",
|
||||
GitHubPulse.isStale(fetchedAt: nil, now: now, maxAge: 60))
|
||||
check("fresh (same instant) → not stale",
|
||||
!GitHubPulse.isStale(fetchedAt: now, now: now, maxAge: 60))
|
||||
check("61 s ago → stale",
|
||||
GitHubPulse.isStale(fetchedAt: now.addingTimeInterval(-61), now: now, maxAge: 60))
|
||||
}
|
||||
|
||||
// ── finish ─────────────────────────────────────────────────────────────
|
||||
if failures == 0 {
|
||||
print("\nAll tests passed.")
|
||||
exit(0)
|
||||
} else {
|
||||
print("\n\(failures) test(s) failed.")
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user