chore: improved formatting and linting

This commit is contained in:
Stefan Hausotte 2026-06-04 11:29:23 +02:00
parent 551e1d01ea
commit 167d61bc06
20 changed files with 120 additions and 107 deletions

View file

@ -3,3 +3,13 @@
disabled_rules: disabled_rules:
- trailing_comma - trailing_comma
- opening_brace - opening_brace
# Service types nest their request/response payload structs one level deeper.
nesting:
type_level: 2
# ForgejoClient intentionally aggregates every authentication flow in one type.
file_length:
warning: 600
type_body_length:
warning: 300

View file

@ -4,7 +4,7 @@ A native Swift library for the [Forgejo](https://forgejo.org) API. ForgejoKit pr
## Features ## Features
- Authentication via username/password (with optional OTP) or API tokens - Authentication via username/password (with optional OTP), API tokens, or OAuth2 bearer tokens
- Repositories: search, list, create, fork, star, file contents, commits, branches, tags, releases - Repositories: search, list, create, fork, star, file contents, commits, branches, tags, releases
- Issues: create, edit, list, comment, manage labels and milestones - Issues: create, edit, list, comment, manage labels and milestones
- Pull requests: create, edit, merge, review, diff - Pull requests: create, edit, merge, review, diff
@ -35,6 +35,9 @@ import ForgejoKit
// Authenticate with a token // Authenticate with a token
let client = ForgejoClient(serverURL: "https://codeberg.org", username: "user", token: "your-token") let client = ForgejoClient(serverURL: "https://codeberg.org", username: "user", token: "your-token")
// Or authenticate with an OAuth2 access token (sent as `Authorization: Bearer`)
let client = ForgejoClient(serverURL: "https://codeberg.org", username: "user", bearerToken: "oauth2-access-token")
// Or log in with username/password to create an API token // Or log in with username/password to create an API token
let result = try await ForgejoClient.login(serverURL: "https://codeberg.org", username: "user", password: "pass") let result = try await ForgejoClient.login(serverURL: "https://codeberg.org", username: "user", password: "pass")
let client = result.client let client = result.client

View file

@ -37,7 +37,6 @@ public final class PullRequestService: Sendable {
let mergeTitleField: String? let mergeTitleField: String?
let mergeMessageField: String? let mergeMessageField: String?
// swiftlint:disable:next nesting
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case method = "Do" case method = "Do"
case deleteBranchAfterMerge = "delete_branch_after_merge" case deleteBranchAfterMerge = "delete_branch_after_merge"
@ -58,7 +57,6 @@ public final class PullRequestService: Sendable {
let oldPosition: Int? let oldPosition: Int?
let newPosition: Int? let newPosition: Int?
// swiftlint:disable:next nesting
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case body case body
case path case path
@ -156,7 +154,6 @@ public final class PullRequestService: Sendable {
_ = try await client.performRequestNoContent(url: url, method: "DELETE", body: jsonData, validateStatus: true) _ = try await client.performRequestNoContent(url: url, method: "DELETE", body: jsonData, validateStatus: true)
} }
// swiftlint:disable:next function_parameter_count
public func mergePullRequest( public func mergePullRequest(
owner: String, repo: String, index: Int, owner: String, repo: String, index: Int,
method: String, title: String? = nil, message: String? = nil, method: String, title: String? = nil, message: String? = nil,

View file

@ -1,7 +1,5 @@
import Foundation import Foundation
// swiftlint:disable file_length
private struct CreateRepositoryPayload: Codable { private struct CreateRepositoryPayload: Codable {
let name: String let name: String
let description: String? let description: String?

View file

@ -116,7 +116,7 @@ public final class WorkflowService: Sendable {
// for any run whose tasks are stored with the (1-based) attempt number. // for any run whose tasks are stored with the (1-based) attempt number.
// The matching GET endpoint 307-redirects to the latest-attempt URL // The matching GET endpoint 307-redirects to the latest-attempt URL
// resolve that first, then POST to the resolved attempt-suffixed URL. // resolve that first, then POST to the resolved attempt-suffixed URL.
let postURL = (try? await client.discoverRedirectLocation(url: baseURL)) ?? baseURL let postURL = await (try? client.discoverRedirectLocation(url: baseURL)) ?? baseURL
let body = try client.encodeRequestBody(ViewRequestBody(logCursors: logCursors)) let body = try client.encodeRequestBody(ViewRequestBody(logCursors: logCursors))
return try await client.performRequest( return try await client.performRequest(
url: postURL, method: "POST", body: body, url: postURL, method: "POST", body: body,

18
Tests/.swiftlint.yml Normal file
View file

@ -0,0 +1,18 @@
# Test fixtures embed large, verbatim JSON payloads and exhaustive decoding
# cases, so length limits are relaxed here. Quality/safety rules stay enabled.
disabled_rules:
- trailing_comma
- opening_brace
file_length:
warning: 1500
error: 2000
type_body_length:
warning: 1300
error: 1500
function_body_length:
warning: 60
error: 100
line_length:
warning: 600
error: 700

View file

@ -1,9 +1,8 @@
@testable import ForgejoKit
import Foundation import Foundation
import Testing import Testing
@testable import ForgejoKit
struct AdminServiceTests { struct AdminServiceTests {
@Test func createUserPayloadEncodesCorrectKeys() throws { @Test func createUserPayloadEncodesCorrectKeys() throws {
let json = """ let json = """
{ {
@ -19,7 +18,7 @@ struct AdminServiceTests {
} }
""" """
let data = Data(json.utf8) let data = Data(json.utf8)
let dict = try JSONSerialization.jsonObject(with: data) as! [String: Any] let dict = try #require(JSONSerialization.jsonObject(with: data) as? [String: Any])
#expect(dict["username"] as? String == "testbot") #expect(dict["username"] as? String == "testbot")
#expect(dict["login_name"] as? String == "testbot") #expect(dict["login_name"] as? String == "testbot")
#expect(dict["must_change_password"] as? Bool == false) #expect(dict["must_change_password"] as? Bool == false)

View file

@ -1,6 +1,6 @@
@testable import ForgejoKit
import Foundation import Foundation
import Testing import Testing
@testable import ForgejoKit
struct BearerCredentialTests { struct BearerCredentialTests {
@Test func bearerCredentialUsesBearerScheme() { @Test func bearerCredentialUsesBearerScheme() {

View file

@ -1,13 +1,12 @@
@testable import ForgejoKit
import Foundation import Foundation
import Testing import Testing
@testable import ForgejoKit
struct DateDecodingTests { struct DateDecodingTests {
private func decoder() -> JSONDecoder { private func decoder() -> JSONDecoder {
let d = JSONDecoder() let jsonDecoder = JSONDecoder()
d.dateDecodingStrategy = forgejoDateDecodingStrategy jsonDecoder.dateDecodingStrategy = forgejoDateDecodingStrategy
return d return jsonDecoder
} }
private struct DateWrapper: Codable { private struct DateWrapper: Codable {
@ -74,9 +73,9 @@ struct DateDecodingTests {
let jsonWithout = #"{"date":"2024-03-10T08:00:00Z"}"# let jsonWithout = #"{"date":"2024-03-10T08:00:00Z"}"#
let jsonWith = #"{"date":"2024-03-10T08:00:00.000Z"}"# let jsonWith = #"{"date":"2024-03-10T08:00:00.000Z"}"#
let d = decoder() let jsonDecoder = decoder()
let resultWithout = try d.decode(DateWrapper.self, from: Data(jsonWithout.utf8)) let resultWithout = try jsonDecoder.decode(DateWrapper.self, from: Data(jsonWithout.utf8))
let resultWith = try d.decode(DateWrapper.self, from: Data(jsonWith.utf8)) let resultWith = try jsonDecoder.decode(DateWrapper.self, from: Data(jsonWith.utf8))
#expect(resultWithout.date == resultWith.date) #expect(resultWithout.date == resultWith.date)
} }

View file

@ -1,9 +1,8 @@
@testable import ForgejoKit
import Foundation import Foundation
import Testing import Testing
@testable import ForgejoKit
struct DiffParserTests { struct DiffParserTests {
// MARK: - Empty and minimal input // MARK: - Empty and minimal input
@Test func parseEmptyString() { @Test func parseEmptyString() {
@ -305,13 +304,13 @@ struct DiffParserTests {
#expect(result.files[0].newName == "hello.py") #expect(result.files[0].newName == "hello.py")
#expect(result.files[0].oldName == "/dev/null") #expect(result.files[0].oldName == "/dev/null")
#expect(result.files[0].hunks[0].lines.filter { $0.type == .addition }.count == 2) #expect(result.files[0].hunks[0].lines.count(where: { $0.type == .addition }) == 2)
#expect(result.files[1].newName == "README.md") #expect(result.files[1].newName == "README.md")
#expect(result.files[1].oldName == "README.md") #expect(result.files[1].oldName == "README.md")
let readmeHunk = result.files[1].hunks[0] let readmeHunk = result.files[1].hunks[0]
#expect(readmeHunk.lines.filter { $0.type == .context }.count == 1) #expect(readmeHunk.lines.count(where: { $0.type == .context }) == 1)
#expect(readmeHunk.lines.filter { $0.type == .addition }.count == 2) #expect(readmeHunk.lines.count(where: { $0.type == .addition }) == 2)
} }
// MARK: - No newline at end of file sentinel // MARK: - No newline at end of file sentinel

View file

@ -1,8 +1,7 @@
import Testing
@testable import ForgejoKit @testable import ForgejoKit
import Testing
struct ErrorMappingTests { struct ErrorMappingTests {
@Test func classifiesHTTPStatusCodes() { @Test func classifiesHTTPStatusCodes() {
#expect(HTTPErrorCategory(statusCode: 401) == .authentication) #expect(HTTPErrorCategory(statusCode: 401) == .authentication)
#expect(HTTPErrorCategory(statusCode: 403) == .permissionDenied) #expect(HTTPErrorCategory(statusCode: 403) == .permissionDenied)

View file

@ -1,9 +1,8 @@
@testable import ForgejoKit
import Foundation import Foundation
import Testing import Testing
@testable import ForgejoKit
struct FileContentTests { struct FileContentTests {
private func makeFileContent(content: String?, encoding: String?) -> FileContent { private func makeFileContent(content: String?, encoding: String?) -> FileContent {
FileContent( FileContent(
name: "test.txt", name: "test.txt",
@ -16,7 +15,7 @@ struct FileContentTests {
downloadUrl: nil, downloadUrl: nil,
type: "file", type: "file",
content: content, content: content,
encoding: encoding encoding: encoding,
) )
} }

View file

@ -1,13 +1,12 @@
@testable import ForgejoKit
import Foundation import Foundation
import Testing import Testing
@testable import ForgejoKit
struct ModelDecodingTests { struct ModelDecodingTests {
private func decoder() -> JSONDecoder { private func decoder() -> JSONDecoder {
let d = JSONDecoder() let jsonDecoder = JSONDecoder()
d.dateDecodingStrategy = forgejoDateDecodingStrategy jsonDecoder.dateDecodingStrategy = forgejoDateDecodingStrategy
return d return jsonDecoder
} }
// MARK: - User // MARK: - User
@ -400,14 +399,14 @@ struct ModelDecodingTests {
"updated_at": "2024-03-02T09:00:00Z" "updated_at": "2024-03-02T09:00:00Z"
} }
""" """
let pr = try decoder().decode(PullRequest.self, from: Data(json.utf8)) let pullRequest = try decoder().decode(PullRequest.self, from: Data(json.utf8))
#expect(pr.number == 42) #expect(pullRequest.number == 42)
#expect(pr.title == "Add feature X") #expect(pullRequest.title == "Add feature X")
#expect(pr.head.ref == "feature-x") #expect(pullRequest.head.ref == "feature-x")
#expect(pr.base.ref == "main") #expect(pullRequest.base.ref == "main")
#expect(pr.mergeable == true) #expect(pullRequest.mergeable == true)
#expect(pr.merged == false) #expect(pullRequest.merged == false)
#expect(pr.draft == false) #expect(pullRequest.draft == false)
} }
@Test func decodesPullRequestWithFullMetadata() throws { @Test func decodesPullRequestWithFullMetadata() throws {
@ -447,18 +446,18 @@ struct ModelDecodingTests {
"updated_at": "2024-03-02T09:00:00Z" "updated_at": "2024-03-02T09:00:00Z"
} }
""" """
let pr = try decoder().decode(PullRequest.self, from: Data(json.utf8)) let pullRequest = try decoder().decode(PullRequest.self, from: Data(json.utf8))
#expect(pr.number == 46) #expect(pullRequest.number == 46)
#expect(pr.labels.count == 2) #expect(pullRequest.labels.count == 2)
#expect(pr.labels[0].name == "enhancement") #expect(pullRequest.labels[0].name == "enhancement")
#expect(pr.milestone?.id == 3) #expect(pullRequest.milestone?.id == 3)
#expect(pr.milestone?.title == "v1.0") #expect(pullRequest.milestone?.title == "v1.0")
#expect(pr.milestone?.dueOn != nil) #expect(pullRequest.milestone?.dueOn != nil)
#expect(pr.assignees?.count == 2) #expect(pullRequest.assignees?.count == 2)
#expect(pr.assignees?[0].login == "assignee1") #expect(pullRequest.assignees?[0].login == "assignee1")
#expect(pr.assignees?[0].fullName == "Assignee One") #expect(pullRequest.assignees?[0].fullName == "Assignee One")
#expect(pr.requestedReviewers?.count == 1) #expect(pullRequest.requestedReviewers?.count == 1)
#expect(pr.requestedReviewers?[0].login == "reviewer1") #expect(pullRequest.requestedReviewers?[0].login == "reviewer1")
} }
@Test func decodesPullRequestWithRequestedReviewers() throws { @Test func decodesPullRequestWithRequestedReviewers() throws {
@ -484,11 +483,11 @@ struct ModelDecodingTests {
] ]
} }
""" """
let pr = try decoder().decode(PullRequest.self, from: Data(json.utf8)) let pullRequest = try decoder().decode(PullRequest.self, from: Data(json.utf8))
#expect(pr.number == 44) #expect(pullRequest.number == 44)
#expect(pr.requestedReviewers?.count == 2) #expect(pullRequest.requestedReviewers?.count == 2)
#expect(pr.requestedReviewers?[0].login == "reviewer1") #expect(pullRequest.requestedReviewers?[0].login == "reviewer1")
#expect(pr.requestedReviewers?[1].fullName == "Reviewer Two") #expect(pullRequest.requestedReviewers?[1].fullName == "Reviewer Two")
} }
@Test func decodesPullRequestWithoutRequestedReviewers() throws { @Test func decodesPullRequestWithoutRequestedReviewers() throws {
@ -507,9 +506,9 @@ struct ModelDecodingTests {
"updated_at": "2024-03-02T09:00:00Z" "updated_at": "2024-03-02T09:00:00Z"
} }
""" """
let pr = try decoder().decode(PullRequest.self, from: Data(json.utf8)) let pullRequest = try decoder().decode(PullRequest.self, from: Data(json.utf8))
#expect(pr.number == 45) #expect(pullRequest.number == 45)
#expect(pr.requestedReviewers == nil) #expect(pullRequest.requestedReviewers == nil)
} }
@Test func decodesMergedPullRequest() throws { @Test func decodesMergedPullRequest() throws {
@ -531,10 +530,10 @@ struct ModelDecodingTests {
"updated_at": "2024-04-01T12:00:00Z" "updated_at": "2024-04-01T12:00:00Z"
} }
""" """
let pr = try decoder().decode(PullRequest.self, from: Data(json.utf8)) let pullRequest = try decoder().decode(PullRequest.self, from: Data(json.utf8))
#expect(pr.merged == true) #expect(pullRequest.merged == true)
#expect(pr.mergedBy?.login == "maintainer") #expect(pullRequest.mergedBy?.login == "maintainer")
#expect(pr.mergedAt != nil) #expect(pullRequest.mergedAt != nil)
} }
// MARK: - PullRequestReview // MARK: - PullRequestReview
@ -607,10 +606,10 @@ struct ModelDecodingTests {
body: "Fix this", body: "Fix this",
path: "file.swift", path: "file.swift",
oldPosition: 5, oldPosition: 5,
newPosition: 7 newPosition: 7,
) )
let data = try JSONEncoder().encode(comment) let data = try JSONEncoder().encode(comment)
let dict = try JSONSerialization.jsonObject(with: data) as! [String: Any] let dict = try #require(JSONSerialization.jsonObject(with: data) as? [String: Any])
#expect(dict["body"] as? String == "Fix this") #expect(dict["body"] as? String == "Fix this")
#expect(dict["path"] as? String == "file.swift") #expect(dict["path"] as? String == "file.swift")
#expect(dict["old_position"] as? Int == 5) #expect(dict["old_position"] as? Int == 5)
@ -622,10 +621,10 @@ struct ModelDecodingTests {
body: "Note", body: "Note",
path: "readme.md", path: "readme.md",
oldPosition: nil, oldPosition: nil,
newPosition: nil newPosition: nil,
) )
let data = try JSONEncoder().encode(comment) let data = try JSONEncoder().encode(comment)
let dict = try JSONSerialization.jsonObject(with: data) as! [String: Any] let dict = try #require(JSONSerialization.jsonObject(with: data) as? [String: Any])
#expect(dict["old_position"] == nil) #expect(dict["old_position"] == nil)
#expect(dict["new_position"] == nil) #expect(dict["new_position"] == nil)
} }

View file

@ -1,9 +1,8 @@
@testable import ForgejoKit
import Foundation import Foundation
import Testing import Testing
@testable import ForgejoKit
struct NormalizeServerURLTests { struct NormalizeServerURLTests {
@Test func stripsTrailingSlash() { @Test func stripsTrailingSlash() {
let result = ForgejoClient.normalizeServerURL("https://forgejo.example.com/") let result = ForgejoClient.normalizeServerURL("https://forgejo.example.com/")
#expect(result == "https://forgejo.example.com") #expect(result == "https://forgejo.example.com")

View file

@ -1,13 +1,12 @@
@testable import ForgejoKit
import Foundation import Foundation
import Testing import Testing
@testable import ForgejoKit
struct NotificationTests { struct NotificationTests {
private func decoder() -> JSONDecoder { private func decoder() -> JSONDecoder {
let d = JSONDecoder() let jsonDecoder = JSONDecoder()
d.dateDecodingStrategy = forgejoDateDecodingStrategy jsonDecoder.dateDecodingStrategy = forgejoDateDecodingStrategy
return d return jsonDecoder
} }
// MARK: - Subject number extraction // MARK: - Subject number extraction

View file

@ -1,9 +1,8 @@
@testable import ForgejoKit
import Foundation import Foundation
import Testing import Testing
@testable import ForgejoKit
struct RepositoryServiceURLTests { struct RepositoryServiceURLTests {
/// Creates a client with dummy credentials for URL construction tests. /// Creates a client with dummy credentials for URL construction tests.
private func makeClient() -> ForgejoClient { private func makeClient() -> ForgejoClient {
ForgejoClient(serverURL: "https://forgejo.example.com", username: "user", password: "pass") ForgejoClient(serverURL: "https://forgejo.example.com", username: "user", password: "pass")
@ -120,7 +119,7 @@ struct RepositoryServiceURLTests {
"auto_init": true, "auto_init": true,
] ]
let data = try JSONSerialization.data(withJSONObject: payload) let data = try JSONSerialization.data(withJSONObject: payload)
let dict = try JSONSerialization.jsonObject(with: data) as! [String: Any] let dict = try #require(JSONSerialization.jsonObject(with: data) as? [String: Any])
#expect(dict["name"] as? String == "test-repo") #expect(dict["name"] as? String == "test-repo")
#expect(dict["auto_init"] as? Bool == true) #expect(dict["auto_init"] as? Bool == true)
#expect(dict["private"] as? Bool == false) #expect(dict["private"] as? Bool == false)
@ -133,7 +132,7 @@ struct RepositoryServiceURLTests {
"new_branch": "feature-branch", "new_branch": "feature-branch",
] ]
let data = try JSONSerialization.data(withJSONObject: payload) let data = try JSONSerialization.data(withJSONObject: payload)
let dict = try JSONSerialization.jsonObject(with: data) as! [String: Any] let dict = try #require(JSONSerialization.jsonObject(with: data) as? [String: Any])
#expect(dict["content"] as? String == "base64content") #expect(dict["content"] as? String == "base64content")
#expect(dict["message"] as? String == "Add file") #expect(dict["message"] as? String == "Add file")
#expect(dict["new_branch"] as? String == "feature-branch") #expect(dict["new_branch"] as? String == "feature-branch")

View file

@ -1,9 +1,8 @@
@testable import ForgejoKit
import Foundation import Foundation
import Testing import Testing
@testable import ForgejoKit
struct URLSessionManagerTests { struct URLSessionManagerTests {
// MARK: - trustedHost scoping // MARK: - trustedHost scoping
@Test func selfSignedWithoutTrustedHostAcceptsAny() { @Test func selfSignedWithoutTrustedHostAcceptsAny() {
@ -62,49 +61,48 @@ struct URLSessionManagerTests {
} }
struct ForgejoClientHostMatchTests { struct ForgejoClientHostMatchTests {
// MARK: - Auth header host matching // MARK: - Auth header host matching
@Test func authenticatedRequestIncludesAuthForMatchingHost() { @Test func authenticatedRequestIncludesAuthForMatchingHost() throws {
let client = ForgejoClient( let client = ForgejoClient(
serverURL: "https://forgejo.example.com", serverURL: "https://forgejo.example.com",
username: "user", username: "user",
password: "pass" password: "pass",
) )
let url = URL(string: "https://forgejo.example.com/api/v1/user")! let url = try #require(URL(string: "https://forgejo.example.com/api/v1/user"))
let request = client.authenticatedRequest(url: url) let request = client.authenticatedRequest(url: url)
#expect(request.value(forHTTPHeaderField: "Authorization") != nil) #expect(request.value(forHTTPHeaderField: "Authorization") != nil)
} }
@Test func authenticatedRequestOmitsAuthForDifferentHost() { @Test func authenticatedRequestOmitsAuthForDifferentHost() throws {
let client = ForgejoClient( let client = ForgejoClient(
serverURL: "https://forgejo.example.com", serverURL: "https://forgejo.example.com",
username: "user", username: "user",
password: "pass" password: "pass",
) )
let url = URL(string: "https://evil.example.com/api/v1/user")! let url = try #require(URL(string: "https://evil.example.com/api/v1/user"))
let request = client.authenticatedRequest(url: url) let request = client.authenticatedRequest(url: url)
#expect(request.value(forHTTPHeaderField: "Authorization") == nil) #expect(request.value(forHTTPHeaderField: "Authorization") == nil)
} }
@Test func authenticatedRequestMatchesCaseInsensitiveHost() { @Test func authenticatedRequestMatchesCaseInsensitiveHost() throws {
let client = ForgejoClient( let client = ForgejoClient(
serverURL: "https://Forgejo.Example.COM", serverURL: "https://Forgejo.Example.COM",
username: "user", username: "user",
password: "pass" password: "pass",
) )
let url = URL(string: "https://forgejo.example.com/api/v1/repos")! let url = try #require(URL(string: "https://forgejo.example.com/api/v1/repos"))
let request = client.authenticatedRequest(url: url) let request = client.authenticatedRequest(url: url)
#expect(request.value(forHTTPHeaderField: "Authorization") != nil) #expect(request.value(forHTTPHeaderField: "Authorization") != nil)
} }
@Test func authenticatedRequestSetsMethodAndBody() { @Test func authenticatedRequestSetsMethodAndBody() throws {
let client = ForgejoClient( let client = ForgejoClient(
serverURL: "https://forgejo.example.com", serverURL: "https://forgejo.example.com",
username: "user", username: "user",
password: "pass" password: "pass",
) )
let url = URL(string: "https://forgejo.example.com/api/v1/repos")! let url = try #require(URL(string: "https://forgejo.example.com/api/v1/repos"))
let body = Data("{\"name\":\"test\"}".utf8) let body = Data("{\"name\":\"test\"}".utf8)
let request = client.authenticatedRequest(url: url, method: "POST", body: body) let request = client.authenticatedRequest(url: url, method: "POST", body: body)
#expect(request.httpMethod == "POST") #expect(request.httpMethod == "POST")
@ -116,7 +114,7 @@ struct ForgejoClientHostMatchTests {
let client = ForgejoClient( let client = ForgejoClient(
serverURL: "https://forgejo.example.com", serverURL: "https://forgejo.example.com",
username: "user", username: "user",
password: "pass" password: "pass",
) )
let url = try client.makeURL(path: "/api/v1/user") let url = try client.makeURL(path: "/api/v1/user")
#expect(url.absoluteString == "https://forgejo.example.com/api/v1/user") #expect(url.absoluteString == "https://forgejo.example.com/api/v1/user")
@ -126,11 +124,11 @@ struct ForgejoClientHostMatchTests {
let client = ForgejoClient( let client = ForgejoClient(
serverURL: "https://forgejo.example.com", serverURL: "https://forgejo.example.com",
username: "user", username: "user",
password: "pass" password: "pass",
) )
let url = try client.makeURL(path: "/api/v1/repos/search", queryItems: [ let url = try client.makeURL(path: "/api/v1/repos/search", queryItems: [
URLQueryItem(name: "q", value: "test"), URLQueryItem(name: "q", value: "test"),
URLQueryItem(name: "page", value: "2") URLQueryItem(name: "page", value: "2"),
]) ])
#expect(url.absoluteString.contains("q=test")) #expect(url.absoluteString.contains("q=test"))
#expect(url.absoluteString.contains("page=2")) #expect(url.absoluteString.contains("page=2"))
@ -142,7 +140,7 @@ struct ForgejoClientHostMatchTests {
serverURL: "https://my-forgejo.local:3000", serverURL: "https://my-forgejo.local:3000",
username: "user", username: "user",
password: "pass", password: "pass",
allowSelfSignedCertificates: true allowSelfSignedCertificates: true,
) )
// The session should be created with self-signed support // The session should be created with self-signed support
#expect(client.serverURL == "https://my-forgejo.local:3000") #expect(client.serverURL == "https://my-forgejo.local:3000")

View file

@ -1,16 +1,15 @@
@testable import ForgejoKit
import Foundation import Foundation
import Testing import Testing
@testable import ForgejoKit
struct UserServiceTests { struct UserServiceTests {
@Test func createTokenPayloadEncodesCorrectKeys() throws { @Test func createTokenPayloadEncodesCorrectKeys() throws {
let payload: [String: Any] = [ let payload: [String: Any] = [
"name": "integration-test-token", "name": "integration-test-token",
"scopes": ["read:user", "write:user"], "scopes": ["read:user", "write:user"],
] ]
let data = try JSONSerialization.data(withJSONObject: payload) let data = try JSONSerialization.data(withJSONObject: payload)
let dict = try JSONSerialization.jsonObject(with: data) as! [String: Any] let dict = try #require(JSONSerialization.jsonObject(with: data) as? [String: Any])
#expect(dict["name"] as? String == "integration-test-token") #expect(dict["name"] as? String == "integration-test-token")
#expect((dict["scopes"] as? [String])?.count == 2) #expect((dict["scopes"] as? [String])?.count == 2)
} }

View file

@ -1,9 +1,8 @@
@testable import ForgejoKit
import Foundation import Foundation
import Testing import Testing
@testable import ForgejoKit
struct WorkflowServiceURLTests { struct WorkflowServiceURLTests {
private func makeClient() -> ForgejoClient { private func makeClient() -> ForgejoClient {
ForgejoClient(serverURL: "https://forgejo.example.com", username: "user", password: "pass") ForgejoClient(serverURL: "https://forgejo.example.com", username: "user", password: "pass")
} }

View file

@ -8,10 +8,10 @@ test:
swift test 2>&1 | xcbeautify swift test 2>&1 | xcbeautify
lint: lint:
swiftlint lint Sources swiftlint lint Sources Tests
format: format:
swiftformat Sources swiftformat Sources Tests
# Tag and push a new release: just release 1.0.0 # Tag and push a new release: just release 1.0.0
release version: release version: