ForgejoKit/Tests/ForgejoKitTests/WorkflowServiceURLTests.swift
Voislav Vasiljevski 3967a1ba79 feat: add Forgejo Actions runs API (#1)
New model:
  - WorkflowRun — mirrors Forgejo's ActionRun schema (id, title, status,
    event, indexInRepo, commitSha, prettyRef, workflowId, created, updated,
    started, stopped, durationNanos, htmlUrl, triggerUser, repository).

New service:
  - WorkflowService
      - fetchRuns(owner:repo:status:event:ref:workflowId:runNumber:headSha:page:limit:)
        backed by GET /repos/{owner}/{repo}/actions/runs.
      - fetchRun(owner:repo:runId:) backed by
        GET /repos/{owner}/{repo}/actions/runs/{run_id}.

Experimental:
  - fetchRunView(owner:repo:runIndex:jobIndex:logCursors:) backed by
    Forgejo's web-UI route POST
    /{owner}/{repo}/actions/runs/{runIndex}/jobs/{jobIndex}/attempt/{N}.
    This is not part of /api/v1 and may change between Forgejo releases;
    it lets clients render jobs, steps, and step logs (via WorkflowRunView,
    WorkflowRunViewJob, WorkflowRunViewStep, WorkflowLogCursor types).
  - ForgejoClient.discoverRedirectLocation(url:) helper that resolves
    Forgejo's RedirectToLatestAttempt without consuming the redirect
    target — used to find the latest attempt number before POSTing.

Co-authored-by: Voislav Vasiljevski <voislav@voioo.cz>
Reviewed-on: https://codeberg.org/secana/ForgejoKit/pulls/1
Reviewed-by: secana <secana@noreply.codeberg.org>
2026-05-07 16:42:16 +02:00

79 lines
3.2 KiB
Swift

import Foundation
import Testing
@testable import ForgejoKit
struct WorkflowServiceURLTests {
private func makeClient() -> ForgejoClient {
ForgejoClient(serverURL: "https://forgejo.example.com", username: "user", password: "pass")
}
@Test func runsListURLNoFilters() throws {
let client = makeClient()
let queryItems = [
URLQueryItem(name: "page", value: "1"),
URLQueryItem(name: "limit", value: "20"),
]
let url = try client.makeRepoURL(
owner: "owner", repo: "repo",
path: "/actions/runs", queryItems: queryItems,
)
#expect(url.path == "/api/v1/repos/owner/repo/actions/runs")
#expect(url.query?.contains("page=1") == true)
#expect(url.query?.contains("limit=20") == true)
}
@Test func runsListURLAllFilters() throws {
let client = makeClient()
let queryItems = [
URLQueryItem(name: "page", value: "2"),
URLQueryItem(name: "limit", value: "50"),
URLQueryItem(name: "status", value: "running"),
URLQueryItem(name: "event", value: "push"),
URLQueryItem(name: "ref", value: "main"),
URLQueryItem(name: "workflow_id", value: "ci.yml"),
URLQueryItem(name: "run_number", value: "42"),
URLQueryItem(name: "head_sha", value: "abc123"),
]
let url = try client.makeRepoURL(
owner: "owner", repo: "repo",
path: "/actions/runs", queryItems: queryItems,
)
#expect(url.query?.contains("status=running") == true)
#expect(url.query?.contains("event=push") == true)
#expect(url.query?.contains("ref=main") == true)
#expect(url.query?.contains("workflow_id=ci.yml") == true)
#expect(url.query?.contains("run_number=42") == true)
#expect(url.query?.contains("head_sha=abc123") == true)
}
@Test func runDetailURL() throws {
let client = makeClient()
let url = try client.makeRepoURL(owner: "owner", repo: "repo", path: "/actions/runs/123")
#expect(url.absoluteString == "https://forgejo.example.com/api/v1/repos/owner/repo/actions/runs/123")
}
@Test func ownerAndRepoArePercentEncoded() throws {
let client = makeClient()
let url = try client.makeRepoURL(owner: "my org", repo: "my repo", path: "/actions/runs")
#expect(url.absoluteString.contains("my%20org"))
#expect(url.absoluteString.contains("my%20repo"))
}
// MARK: - Experimental run-view URL (web routes, not /api/v1)
@Test func runViewURLIsUnderRepoWebRoute() throws {
let client = makeClient()
let url = try client.makeURL(path: "/owner/repo/actions/runs/12/jobs/0")
#expect(url.absoluteString == "https://forgejo.example.com/owner/repo/actions/runs/12/jobs/0")
}
@Test func runViewURLEncodesOwnerAndRepo() throws {
let client = makeClient()
let owner = ForgejoClient.encodedPathSegment("my org")
let repo = ForgejoClient.encodedPathSegment("my repo")
let url = try client.makeURL(path: "/\(owner)/\(repo)/actions/runs/3/jobs/1")
#expect(url.absoluteString.contains("my%20org"))
#expect(url.absoluteString.contains("my%20repo"))
}
}