diff --git a/Sources/App/Core/Gitlab.swift b/Sources/App/Core/Gitlab.swift index 14f50080a..baca2a87a 100644 --- a/Sources/App/Core/Gitlab.swift +++ b/Sources/App/Core/Gitlab.swift @@ -105,8 +105,12 @@ extension Gitlab.Builder { "VERSION_ID": versionID.uuidString ] ) - let body = try JSONEncoder().encode(dto) - let response = try await httpClient.post(url: "\(projectURL)/trigger/pipeline", body: body) + let body = try URLEncodedFormEncoder().encode(dto) + let response = try await httpClient.post( + url: "\(projectURL)/trigger/pipeline", + headers: .contentTypeFormURLEncoded, + body: Data(body.utf8) + ) do { guard let body = response.body else { throw Gitlab.Error.noBody } @@ -205,6 +209,10 @@ private extension HTTPHeaders { static func bearer(_ token: String) -> Self { .init([("Authorization", "Bearer \(token)")]) } + + static var contentTypeFormURLEncoded: Self { + .init([("Content-Type", "application/x-www-form-urlencoded")]) + } } diff --git a/Tests/AppTests/BuildTests.swift b/Tests/AppTests/BuildTests.swift index 0e4048d15..70b1ecaf8 100644 --- a/Tests/AppTests/BuildTests.swift +++ b/Tests/AppTests/BuildTests.swift @@ -145,7 +145,7 @@ class BuildTests: AppTestCase { called.setTrue() let body = try XCTUnwrap(body) XCTAssertEqual( - try JSONDecoder().decode(Gitlab.Builder.PostDTO.self, from: body), + try URLEncodedFormDecoder().decode(Gitlab.Builder.PostDTO.self, from: body), .init(token: "pipeline token", ref: "main", variables: [ @@ -201,7 +201,7 @@ class BuildTests: AppTestCase { called.setTrue() let body = try XCTUnwrap(body) // only test the TIMEOUT value, the rest is already tested in `test_trigger` above - let response = try? JSONDecoder().decode(Gitlab.Builder.PostDTO.self, from: body) + let response = try? URLEncodedFormDecoder().decode(Gitlab.Builder.PostDTO.self, from: body) XCTAssertNotNil(response) XCTAssertEqual(response?.variables["TIMEOUT"], "15m") return try .created(jsonEncode: Gitlab.Builder.Response.init(webUrl: "http://web_url")) diff --git a/Tests/AppTests/BuildTriggerTests.swift b/Tests/AppTests/BuildTriggerTests.swift index cedb9540d..70cb3a3f8 100644 --- a/Tests/AppTests/BuildTriggerTests.swift +++ b/Tests/AppTests/BuildTriggerTests.swift @@ -341,6 +341,7 @@ class BuildTriggerTests: AppTestCase { } func test_triggerBuildsUnchecked() async throws { + let queries = QueueIsolated<[Gitlab.Builder.PostDTO]>([]) try await withDependencies { $0.environment.awsDocsBucket = { "awsDocsBucket" } $0.environment.builderToken = { "builder token" } @@ -349,26 +350,15 @@ class BuildTriggerTests: AppTestCase { $0.environment.siteURL = { "http://example.com" } // Use live dependency but replace actual client with a mock so we can // assert on the details being sent without actually making a request - $0.buildSystem.triggerBuild = { @Sendable buildId, cloneURL, isDocBuild, platform, ref, swiftVersion, versionID in - try await Gitlab.Builder.triggerBuild(buildId: buildId, - cloneURL: cloneURL, - isDocBuild: isDocBuild, - platform: platform, - reference: ref, - swiftVersion: swiftVersion, - versionID: versionID) + $0.buildSystem.triggerBuild = BuildSystemClient.liveValue.triggerBuild + $0.httpClient.post = { @Sendable _, _, body in + let body = try XCTUnwrap(body) + let query = try URLEncodedFormDecoder().decode(Gitlab.Builder.PostDTO.self, from: body) + queries.withValue { $0.append(query) } + return try .created(jsonEncode: Gitlab.Builder.Response.init(webUrl: "http://web_url")) } } operation: { // setup - let queries = QueueIsolated<[Gitlab.Builder.PostDTO]>([]) - let client = MockClient { req, res in - guard let query = try? req.query.decode(Gitlab.Builder.PostDTO.self) else { return } - queries.withValue { $0.append(query) } - try? res.content.encode( - Gitlab.Builder.Response.init(webUrl: "http://web_url") - ) - } - let versionId = UUID() do { // save package with partially completed builds let p = Package(id: UUID(), url: "2") @@ -381,7 +371,7 @@ class BuildTriggerTests: AppTestCase { // MUT try await triggerBuildsUnchecked(on: app.db, - client: client, + client: app.client, triggers: triggers) // validate diff --git a/Tests/AppTests/GitlabBuilderTests.swift b/Tests/AppTests/GitlabBuilderTests.swift index 61b20b7d5..6b45d695e 100644 --- a/Tests/AppTests/GitlabBuilderTests.swift +++ b/Tests/AppTests/GitlabBuilderTests.swift @@ -175,11 +175,13 @@ class LiveGitlabBuilderTests: AppTestCase { // Set this to a valid value if you want to report build results back to the server ProcessInfo.processInfo.environment["LIVE_BUILDER_TOKEN"] } + $0.environment.buildTimeout = { 10 } $0.environment.gitlabPipelineToken = { // This Gitlab token is required in order to trigger the pipeline ProcessInfo.processInfo.environment["LIVE_GITLAB_PIPELINE_TOKEN"] } $0.environment.siteURL = { "https://staging.swiftpackageindex.com" } + $0.httpClient = .liveValue } operation: { // set build branch to trigger on Gitlab.Builder.branch = "main" @@ -198,7 +200,8 @@ class LiveGitlabBuilderTests: AppTestCase { platform: .macosSpm, reference: .tag(.init(0, 3, 2)), swiftVersion: .v4, - versionID: versionID) + versionID: versionID + ) print("status: \(res.status)") print("buildId: \(buildId)") diff --git a/Tests/AppTests/Helpers/URLEncodedFormDecoder+ext.swift b/Tests/AppTests/Helpers/URLEncodedFormDecoder+ext.swift new file mode 100644 index 000000000..568b80b2d --- /dev/null +++ b/Tests/AppTests/Helpers/URLEncodedFormDecoder+ext.swift @@ -0,0 +1,22 @@ +// Copyright Dave Verwer, Sven A. Schmidt, and other contributors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Vapor + + +extension URLEncodedFormDecoder { + func decode(_: D.Type, from data: Data) throws -> D { + try self.decode(D.self, from: String(decoding: data, as: UTF8.self), userInfo: [:]) + } +}