mirror of
https://codeberg.org/secana/ForgejoKit.git
synced 2026-06-16 05:13:53 -07:00
chore: improved formatting and linting
This commit is contained in:
parent
551e1d01ea
commit
167d61bc06
20 changed files with 120 additions and 107 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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?
|
||||||
|
|
|
||||||
|
|
@ -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
18
Tests/.swiftlint.yml
Normal 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
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
justfile
4
justfile
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue