Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions Sources/App/Core/AppEnvironment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ struct AppEnvironment: Sendable {
var awsDocsBucket: @Sendable () -> String?
var awsReadmeBucket: @Sendable () -> String?
var awsSecretAccessKey: @Sendable () -> String?
var buildTimeout: @Sendable () -> Int
var builderToken: @Sendable () -> String?
var buildTriggerAllowList: @Sendable () -> [Package.Id]
var buildTriggerDownscaling: @Sendable () -> Double
var buildTriggerLatestSwiftVersionDownscaling: @Sendable () -> Double
Expand Down Expand Up @@ -112,8 +110,6 @@ extension AppEnvironment {
awsDocsBucket: { Environment.get("AWS_DOCS_BUCKET") },
awsReadmeBucket: { Environment.get("AWS_README_BUCKET") },
awsSecretAccessKey: { Environment.get("AWS_SECRET_ACCESS_KEY") },
buildTimeout: { Environment.get("BUILD_TIMEOUT").flatMap(Int.init) ?? 10 },
builderToken: { Environment.get("BUILDER_TOKEN") },
buildTriggerAllowList: {
Environment.get("BUILD_TRIGGER_ALLOW_LIST")
.map { Data($0.utf8) }
Expand Down
6 changes: 4 additions & 2 deletions Sources/App/Core/Authentication/User.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

import Authentication
import Dependencies
import JWTKit
import Vapor
import VaporToOpenAPI
Expand Down Expand Up @@ -54,9 +55,10 @@ extension User {
static var builder: Self { .init(name: "builder", identifier: "builder") }

struct BuilderAuthenticator: AsyncBearerAuthenticator {
@Dependency(\.environment) var environment

func authenticate(bearer: BearerAuthorization, for request: Request) async throws {
if let builderToken = Current.builderToken(),
bearer.token == builderToken {
if let token = environment.builderToken(), bearer.token == token {
request.auth.login(User.builder)
}
}
Expand Down
4 changes: 4 additions & 0 deletions Sources/App/Core/Dependencies/EnvironmentClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ struct EnvironmentClient {
// regarding the use of XCTFail here.
var allowBuildTriggers: @Sendable () -> Bool = { XCTFail(#function); return true }
var allowSocialPosts: @Sendable () -> Bool = { XCTFail(#function); return true }
var builderToken: @Sendable () -> String?
var buildTimeout: @Sendable () -> Int = { XCTFail(#function); return 10 }
// We're not defaulting current to XCTFail, because its use is too pervasive and would require the vast
// majority of tests to be wrapped with `withDependencies`.
// We can do so at a later time once more tests are transitioned over for other dependencies. This is
Expand All @@ -45,6 +47,8 @@ extension EnvironmentClient: DependencyKey {
.flatMap(\.asBool)
?? Constants.defaultAllowSocialPosts
},
builderToken: { Environment.get("BUILDER_TOKEN") },
buildTimeout: { Environment.get("BUILD_TIMEOUT").flatMap(Int.init) ?? 10 },
current: { (try? Environment.detect()) ?? .development },
mastodonCredentials: {
Environment.get("MASTODON_ACCESS_TOKEN")
Expand Down
7 changes: 5 additions & 2 deletions Sources/App/Core/Gitlab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import Dependencies
import Vapor


Expand Down Expand Up @@ -76,13 +77,15 @@ extension Gitlab.Builder {
reference: Reference,
swiftVersion: SwiftVersion,
versionID: Version.Id) async throws -> Build.TriggerResponse {
@Dependency(\.environment) var environment

guard let pipelineToken = Current.gitlabPipelineToken(),
let builderToken = Current.builderToken()
let builderToken = environment.builderToken()
else { throw Gitlab.Error.missingToken }
guard let awsDocsBucket = Current.awsDocsBucket() else {
throw Gitlab.Error.missingConfiguration("AWS_DOCS_BUCKET")
}
let timeout = Current.buildTimeout() + (isDocBuild ? 5 : 0)
let timeout = environment.buildTimeout() + (isDocBuild ? 5 : 0)

let uri: URI = .init(string: "\(projectURL)/trigger/pipeline")
let response = try await client
Expand Down
963 changes: 499 additions & 464 deletions Tests/AppTests/ApiTests.swift

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions Tests/AppTests/AppTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

@testable import App

import Dependencies
import NIOConcurrencyHelpers
import PostgresNIO
import SQLKit
Expand All @@ -35,8 +36,16 @@ class AppTestCase: XCTestCase {
}

func setup(_ environment: Environment) async throws -> Application {
try await Self.setupDb(environment)
return try await Self.setupApp(environment)
try await withDependencies {
// Setting builderToken here when it's also set in all tests may seem redundant but it's
// what allows test_post_buildReport_large to work.
// See https://github.com/pointfreeco/swift-dependencies/discussions/300#discussioncomment-11252906
// for details.
$0.environment.builderToken = { "secr3t" }
} operation: {
try await Self.setupDb(environment)
return try await Self.setupApp(environment)
}
}

override func tearDown() async throws {
Expand Down
223 changes: 116 additions & 107 deletions Tests/AppTests/BuildTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

@testable import App

import Dependencies
import Fluent
import PostgresNIO
import SQLKit
Expand Down Expand Up @@ -128,118 +129,126 @@ class BuildTests: AppTestCase {
}

func test_trigger() async throws {
Current.builderToken = { "builder token" }
Current.gitlabPipelineToken = { "pipeline token" }
Current.siteURL = { "http://example.com" }
// setup
let p = try await savePackage(on: app.db, "1")
let v = try Version(package: p, reference: .branch("main"))
try await v.save(on: app.db)
let buildId = UUID()
let versionID = try XCTUnwrap(v.id)

// Use live dependency but replace actual client with a mock so we can
// assert on the details being sent without actually making a request
Current.triggerBuild = { client, buildId, cloneURL, isDocBuild, platform, ref, swiftVersion, versionID in
try await Gitlab.Builder.triggerBuild(client: client,
buildId: buildId,
cloneURL: cloneURL,
isDocBuild: isDocBuild,
platform: platform,
reference: ref,
swiftVersion: swiftVersion,
versionID: versionID)
}
var called = false
let client = MockClient { req, res in
called = true
res.status = .created
try? res.content.encode(
Gitlab.Builder.Response.init(webUrl: "http://web_url")
)
// validate request data
XCTAssertEqual(try? req.query.decode(Gitlab.Builder.PostDTO.self),
Gitlab.Builder.PostDTO(
token: "pipeline token",
ref: "main",
variables: [
"API_BASEURL": "http://example.com/api",
"AWS_DOCS_BUCKET": "awsDocsBucket",
"BUILD_ID": buildId.uuidString,
"BUILD_PLATFORM": "macos-xcodebuild",
"BUILDER_TOKEN": "builder token",
"CLONE_URL": "1",
"REFERENCE": "main",
"SWIFT_VERSION": "5.2",
"TIMEOUT": "10m",
"VERSION_ID": versionID.uuidString,
]))
}

// MUT
let res = try await Build.trigger(database: app.db,
client: client,
buildId: buildId,
isDocBuild: false,
platform: .macosXcodebuild,
swiftVersion: .init(5, 2, 4),
versionId: versionID)
try await withDependencies {
$0.environment.builderToken = { "builder token" }
$0.environment.buildTimeout = { 10 }
} operation: {
Current.gitlabPipelineToken = { "pipeline token" }
Current.siteURL = { "http://example.com" }
// setup
let p = try await savePackage(on: app.db, "1")
let v = try Version(package: p, reference: .branch("main"))
try await v.save(on: app.db)
let buildId = UUID()
let versionID = try XCTUnwrap(v.id)

// Use live dependency but replace actual client with a mock so we can
// assert on the details being sent without actually making a request
Current.triggerBuild = { client, buildId, cloneURL, isDocBuild, platform, ref, swiftVersion, versionID in
try await Gitlab.Builder.triggerBuild(client: client,
buildId: buildId,
cloneURL: cloneURL,
isDocBuild: isDocBuild,
platform: platform,
reference: ref,
swiftVersion: swiftVersion,
versionID: versionID)
}
var called = false
let client = MockClient { req, res in
called = true
res.status = .created
try? res.content.encode(
Gitlab.Builder.Response.init(webUrl: "http://web_url")
)
// validate request data
XCTAssertEqual(try? req.query.decode(Gitlab.Builder.PostDTO.self),
Gitlab.Builder.PostDTO(
token: "pipeline token",
ref: "main",
variables: [
"API_BASEURL": "http://example.com/api",
"AWS_DOCS_BUCKET": "awsDocsBucket",
"BUILD_ID": buildId.uuidString,
"BUILD_PLATFORM": "macos-xcodebuild",
"BUILDER_TOKEN": "builder token",
"CLONE_URL": "1",
"REFERENCE": "main",
"SWIFT_VERSION": "5.2",
"TIMEOUT": "10m",
"VERSION_ID": versionID.uuidString,
]))
}

// validate
XCTAssertTrue(called)
XCTAssertEqual(res.status, .created)
// MUT
let res = try await Build.trigger(database: app.db,
client: client,
buildId: buildId,
isDocBuild: false,
platform: .macosXcodebuild,
swiftVersion: .init(5, 2, 4),
versionId: versionID)

// validate
XCTAssertTrue(called)
XCTAssertEqual(res.status, .created)
}
}

func test_trigger_isDocBuild() async throws {
// Same test as test_trigger above, except we trigger with isDocBuild: true
// and expect a 15m TIMEOUT instead of 10m
Current.builderToken = { "builder token" }
Current.gitlabPipelineToken = { "pipeline token" }
Current.siteURL = { "http://example.com" }
// setup
let p = try await savePackage(on: app.db, "1")
let v = try Version(package: p, reference: .branch("main"))
try await v.save(on: app.db)
let buildId = UUID()
let versionID = try XCTUnwrap(v.id)

// Use live dependency but replace actual client with a mock so we can
// assert on the details being sent without actually making a request
Current.triggerBuild = { client, buildId, cloneURL, isDocBuild, platform, ref, swiftVersion, versionID in
try await Gitlab.Builder.triggerBuild(client: client,
buildId: buildId,
cloneURL: cloneURL,
isDocBuild: isDocBuild,
platform: platform,
reference: ref,
swiftVersion: swiftVersion,
versionID: versionID)
}
var called = false
let client = MockClient { req, res in
called = true
res.status = .created
try? res.content.encode(
Gitlab.Builder.Response.init(webUrl: "http://web_url")
)
// only test the TIMEOUT value, the rest is already tested in `test_trigger` above
let response = try? req.query.decode(Gitlab.Builder.PostDTO.self)
XCTAssertNotNil(response)
XCTAssertEqual(response?.variables["TIMEOUT"], "15m")
}

// MUT
let res = try await Build.trigger(database: app.db,
client: client,
buildId: buildId,
isDocBuild: true,
platform: .macosXcodebuild,
swiftVersion: .init(5, 2, 4),
versionId: versionID)
try await withDependencies {
$0.environment.builderToken = { "builder token" }
$0.environment.buildTimeout = { 10 }
} operation: {
// Same test as test_trigger above, except we trigger with isDocBuild: true
// and expect a 15m TIMEOUT instead of 10m
Current.gitlabPipelineToken = { "pipeline token" }
Current.siteURL = { "http://example.com" }
// setup
let p = try await savePackage(on: app.db, "1")
let v = try Version(package: p, reference: .branch("main"))
try await v.save(on: app.db)
let buildId = UUID()
let versionID = try XCTUnwrap(v.id)

// Use live dependency but replace actual client with a mock so we can
// assert on the details being sent without actually making a request
Current.triggerBuild = { client, buildId, cloneURL, isDocBuild, platform, ref, swiftVersion, versionID in
try await Gitlab.Builder.triggerBuild(client: client,
buildId: buildId,
cloneURL: cloneURL,
isDocBuild: isDocBuild,
platform: platform,
reference: ref,
swiftVersion: swiftVersion,
versionID: versionID)
}
var called = false
let client = MockClient { req, res in
called = true
res.status = .created
try? res.content.encode(
Gitlab.Builder.Response.init(webUrl: "http://web_url")
)
// only test the TIMEOUT value, the rest is already tested in `test_trigger` above
let response = try? req.query.decode(Gitlab.Builder.PostDTO.self)
XCTAssertNotNil(response)
XCTAssertEqual(response?.variables["TIMEOUT"], "15m")
}

// validate
XCTAssertTrue(called)
XCTAssertEqual(res.status, .created)
// MUT
let res = try await Build.trigger(database: app.db,
client: client,
buildId: buildId,
isDocBuild: true,
platform: .macosXcodebuild,
swiftVersion: .init(5, 2, 4),
versionId: versionID)

// validate
XCTAssertTrue(called)
XCTAssertEqual(res.status, .created)
}
}

func test_query() async throws {
Expand Down
Loading
Loading