Skip to content

Add integration tests #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 27, 2025
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
47 changes: 47 additions & 0 deletions .github/workflows/integration-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Integration Test
on:
workflow_call:
push:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-integration-test
cancel-in-progress: true
jobs:
integration-test:
name: Integration Test
runs-on: ubuntu-22.04
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
toolchain: [latest]
steps:
- name: Install Swift
uses: vapor/swiftly-action@v0.1
with:
toolchain: ${{ matrix.toolchain }}
env:
SWIFTLY_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@v4.2.2
- name: Resolve Swift dependencies
run: swift package resolve
working-directory: ./IntegrationTests
- name: Start Services
run: docker compose up -d
working-directory: ./IntegrationTests
- name: Run Integration Tests
run: swift test --parallel
working-directory: ./IntegrationTests
- name: Export service logs
if: always()
working-directory: ./IntegrationTests
run: |
docker compose logs --no-color > docker-compose-logs.txt
docker compose down
- name: Upload service logs
uses: actions/upload-artifact@v4.6.0
if: failure()
with:
name: docker-compose-logs.txt
path: IntegrationTests/docker-compose-logs.txt
4 changes: 4 additions & 0 deletions .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ jobs:
name: Unit Test
uses: ./.github/workflows/unit-test.yaml
secrets: inherit

integration_test:
name: Integration Test
uses: ./.github/workflows/integration-test.yaml
1 change: 1 addition & 0 deletions .licenseignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*.txt
*.yaml
Package.swift
*/Package.swift
.gitmodules
protocol/
Makefile
Expand Down
19 changes: 19 additions & 0 deletions IntegrationTests/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// swift-tools-version:6.0
import PackageDescription

let package = Package(
name: "swift-ofrep-integration-tests",
platforms: [.macOS(.v15)],
dependencies: [
.package(path: "../")
],
targets: [
.testTarget(
name: "Integration",
dependencies: [
.product(name: "OFREP", package: "swift-ofrep")
]
)
],
swiftLanguageModes: [.v6]
)
136 changes: 136 additions & 0 deletions IntegrationTests/Tests/Integration/OFREPIntegrationTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift OpenFeature open source project
//
// Copyright (c) 2025 the Swift OpenFeature project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Foundation
import OFREP
import OpenFeature
import ServiceLifecycle
import Testing

@testable import Logging

@Suite("OFREP Integration Tests")
struct OFREPIntegrationTests {
@Suite("Bool Flag Resolution")
struct BoolResolutionTests {
@Test("Static", arguments: [("static-on", true), ("static-off", false)])
func staticBool(flag: String, expectedValue: Bool) async throws {
let provider = OFREPProvider(serverURL: URL(string: "http://localhost:8016")!)

try await withOFREPProvider(provider) {
let resolution = OpenFeatureResolution(
value: expectedValue,
error: nil,
reason: .static,
variant: expectedValue ? "on" : "off",
flagMetadata: [:]
)
await #expect(provider.resolution(of: flag, defaultValue: !expectedValue, context: nil) == resolution)
}
}

@Test("Targeting Match")
func targetingMatch() async throws {
let provider = OFREPProvider(serverURL: URL(string: "http://localhost:8016")!)

try await withOFREPProvider(provider) {
let resolution = OpenFeatureResolution(
value: true,
error: nil,
reason: .targetingMatch,
variant: "on",
flagMetadata: [:]
)
let flag = "targeting-on"
let context = OpenFeatureEvaluationContext(targetingKey: "swift")
await #expect(provider.resolution(of: flag, defaultValue: false, context: context) == resolution)
}
}

@Test("No Targeting Match")
func noTargetingMatch() async throws {
let provider = OFREPProvider(serverURL: URL(string: "http://localhost:8016")!)

try await withOFREPProvider(provider) {
let resolution = OpenFeatureResolution(
value: false,
error: nil,
reason: .default,
variant: "off",
flagMetadata: [:]
)
let flag = "targeting-on"
await #expect(provider.resolution(of: flag, defaultValue: true, context: nil) == resolution)
}
}

@Test("Type mismatch", arguments: [true, false])
func typeMismatch(defaultValue: Bool) async throws {
let provider = OFREPProvider(serverURL: URL(string: "http://localhost:8016")!)

try await withOFREPProvider(provider) {
let resolution = OpenFeatureResolution(
value: defaultValue,
error: OpenFeatureResolutionError(
code: .typeMismatch,
message: #"Expected flag value of type "Bool" but received "String"."#
),
reason: .error,
variant: "a"
)
let flag = "static-a-b"
await #expect(provider.resolution(of: flag, defaultValue: defaultValue, context: nil) == resolution)
}
}
}

@Test("Flag not found", arguments: [true, false])
func flagNotFound(defaultValue: Bool) async throws {
let provider = OFREPProvider(serverURL: URL(string: "http://localhost:8016")!)

try await withOFREPProvider(provider) {
let resolution = OpenFeatureResolution(
value: defaultValue,
error: OpenFeatureResolutionError(code: .flagNotFound, message: "flag `💩` does not exist"),
reason: .error
)
await #expect(provider.resolution(of: "💩", defaultValue: defaultValue, context: nil) == resolution)
}
}
}

private func withOFREPProvider<Transport: OFREPClientTransport>(
_ provider: OFREPProvider<Transport>,
perform integrationTest: @escaping @Sendable () async throws -> Void
) async throws {
let integrationTestService = IntegrationTestService(test: integrationTest)
let group = ServiceGroup(
configuration: ServiceGroupConfiguration(
services: [
.init(service: provider),
.init(service: integrationTestService, successTerminationBehavior: .gracefullyShutdownGroup),
],
logger: Logger(label: #function)
)
)

try await group.run()
}

private struct IntegrationTestService: Service {
let test: @Sendable () async throws -> Void

func run() async throws {
try await test()
}
}
14 changes: 14 additions & 0 deletions IntegrationTests/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: swift-ofrep-integration-test
services:
flagd:
image: ghcr.io/open-feature/flagd:latest
ports:
- 8016:8016 # OFREP
volumes:
- ./integration.flagd.json:/etc/flagd/integration.flagd.json
command: [
"start",
"--uri",
"file:./etc/flagd/integration.flagd.json",
"--debug"
]
50 changes: 50 additions & 0 deletions IntegrationTests/integration.flagd.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"$schema": "https://flagd.dev/schema/v0/flags.json",
"flags": {
"static-on": {
"state": "ENABLED",
"variants": {
"on": true,
"off": false
},
"defaultVariant": "on"
},
"static-off": {
"state": "ENABLED",
"variants": {
"on": true,
"off": false
},
"defaultVariant": "off"
},
"targeting-on": {
"state": "ENABLED",
"variants": {
"on": true,
"off": false
},
"defaultVariant": "off",
"targeting": {
"if": [
{
"===": [
{
"var": "targetingKey"
},
"swift"
]
},
"on"
]
}
},
"static-a-b": {
"state": "ENABLED",
"variants": {
"a": "a",
"b": "b"
},
"defaultVariant": "a"
}
}
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Swift OFREP

[![Unit Test](https://github.com/swift-open-feature/swift-ofrep/actions/workflows/unit-test.yaml/badge.svg)](https://github.com/swift-open-feature/swift-ofrep/actions/workflows/unit-test.yaml)
[![Integration Test](https://github.com/swift-open-feature/swift-ofrep/actions/workflows/integration-test.yaml/badge.svg)](https://github.com/swift-open-feature/swift-ofrep/actions/workflows/integration-test.yaml)
[![codecov](https://codecov.io/gh/swift-open-feature/swift-ofrep/graph/badge.svg?token=YK7Y25KOFU)](https://codecov.io/gh/swift-open-feature/swift-ofrep)

A cross-platform [OFREP](https://github.com/open-feature/protocol) provider for Swift,
Expand Down