Skip to content

Commit ddb13c4

Browse files
committed
Add integration tests package
1 parent 457e0b6 commit ddb13c4

File tree

5 files changed

+220
-0
lines changed

5 files changed

+220
-0
lines changed

.licenseignore

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*.txt
99
*.yaml
1010
Package.swift
11+
*/Package.swift
1112
.gitmodules
1213
protocol/
1314
Makefile

IntegrationTests/Package.swift

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// swift-tools-version:6.0
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "swift-ofrep-integration-tests",
6+
platforms: [.macOS(.v15)],
7+
dependencies: [
8+
.package(path: "../")
9+
],
10+
targets: [
11+
.testTarget(
12+
name: "Integration",
13+
dependencies: [
14+
.product(name: "OFREP", package: "swift-ofrep")
15+
]
16+
)
17+
],
18+
swiftLanguageModes: [.v6]
19+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift OpenFeature open source project
4+
//
5+
// Copyright (c) 2025 the Swift OpenFeature project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
import Foundation
15+
import OFREP
16+
import OpenFeature
17+
import ServiceLifecycle
18+
import Testing
19+
20+
@testable import Logging
21+
22+
@Suite("OFREP Integration Tests")
23+
struct OFREPIntegrationTests {
24+
@Suite("Bool Flag Resolution")
25+
struct BoolResolutionTests {
26+
@Test("Static", arguments: [("static-on", true), ("static-off", false)])
27+
func staticBool(flag: String, expectedValue: Bool) async throws {
28+
let provider = OFREPProvider(serverURL: URL(string: "http://localhost:8016")!)
29+
30+
try await withOFREPProvider(provider) {
31+
let resolution = OpenFeatureResolution(
32+
value: expectedValue,
33+
error: nil,
34+
reason: .static,
35+
variant: expectedValue ? "on" : "off",
36+
flagMetadata: [:]
37+
)
38+
await #expect(provider.resolution(of: flag, defaultValue: !expectedValue, context: nil) == resolution)
39+
}
40+
}
41+
42+
@Test("Targeting Match")
43+
func targetingMatch() async throws {
44+
let provider = OFREPProvider(serverURL: URL(string: "http://localhost:8016")!)
45+
46+
try await withOFREPProvider(provider) {
47+
let resolution = OpenFeatureResolution(
48+
value: true,
49+
error: nil,
50+
reason: .targetingMatch,
51+
variant: "on",
52+
flagMetadata: [:]
53+
)
54+
let flag = "targeting-on"
55+
let context = OpenFeatureEvaluationContext(targetingKey: "swift")
56+
await #expect(provider.resolution(of: flag, defaultValue: false, context: context) == resolution)
57+
}
58+
}
59+
60+
@Test("No Targeting Match")
61+
func noTargetingMatch() async throws {
62+
let provider = OFREPProvider(serverURL: URL(string: "http://localhost:8016")!)
63+
64+
try await withOFREPProvider(provider) {
65+
let resolution = OpenFeatureResolution(
66+
value: false,
67+
error: nil,
68+
reason: .default,
69+
variant: "off",
70+
flagMetadata: [:]
71+
)
72+
let flag = "targeting-on"
73+
await #expect(provider.resolution(of: flag, defaultValue: true, context: nil) == resolution)
74+
}
75+
}
76+
77+
@Test("Type mismatch", arguments: [true, false])
78+
func typeMismatch(defaultValue: Bool) async throws {
79+
let provider = OFREPProvider(serverURL: URL(string: "http://localhost:8016")!)
80+
81+
try await withOFREPProvider(provider) {
82+
let resolution = OpenFeatureResolution(
83+
value: defaultValue,
84+
error: OpenFeatureResolutionError(
85+
code: .typeMismatch,
86+
message: #"Expected flag value of type "Bool" but received "String"."#
87+
),
88+
reason: .error,
89+
variant: "a"
90+
)
91+
let flag = "static-a-b"
92+
await #expect(provider.resolution(of: flag, defaultValue: defaultValue, context: nil) == resolution)
93+
}
94+
}
95+
}
96+
97+
@Test("Flag not found", arguments: [true, false])
98+
func flagNotFound(defaultValue: Bool) async throws {
99+
let provider = OFREPProvider(serverURL: URL(string: "http://localhost:8016")!)
100+
101+
try await withOFREPProvider(provider) {
102+
let resolution = OpenFeatureResolution(
103+
value: defaultValue,
104+
error: OpenFeatureResolutionError(code: .flagNotFound, message: "flag `💩` does not exist"),
105+
reason: .error
106+
)
107+
await #expect(provider.resolution(of: "💩", defaultValue: defaultValue, context: nil) == resolution)
108+
}
109+
}
110+
}
111+
112+
private func withOFREPProvider<Transport: OFREPClientTransport>(
113+
_ provider: OFREPProvider<Transport>,
114+
perform integrationTest: @escaping @Sendable () async throws -> Void
115+
) async throws {
116+
let integrationTestService = IntegrationTestService(test: integrationTest)
117+
let group = ServiceGroup(
118+
configuration: ServiceGroupConfiguration(
119+
services: [
120+
.init(service: provider),
121+
.init(service: integrationTestService, successTerminationBehavior: .gracefullyShutdownGroup),
122+
],
123+
logger: Logger(label: #function)
124+
)
125+
)
126+
127+
try await group.run()
128+
}
129+
130+
private struct IntegrationTestService: Service {
131+
let test: @Sendable () async throws -> Void
132+
133+
func run() async throws {
134+
try await test()
135+
}
136+
}

IntegrationTests/docker-compose.yaml

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: swift-ofrep-integration-test
2+
services:
3+
flagd:
4+
image: ghcr.io/open-feature/flagd:latest
5+
ports:
6+
- 8016:8016 # OFREP
7+
volumes:
8+
- ./integration.flagd.json:/etc/flagd/integration.flagd.json
9+
command: [
10+
"start",
11+
"--uri",
12+
"file:./etc/flagd/integration.flagd.json",
13+
"--debug"
14+
]
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"$schema": "https://flagd.dev/schema/v0/flags.json",
3+
"flags": {
4+
"static-on": {
5+
"state": "ENABLED",
6+
"variants": {
7+
"on": true,
8+
"off": false
9+
},
10+
"defaultVariant": "on"
11+
},
12+
"static-off": {
13+
"state": "ENABLED",
14+
"variants": {
15+
"on": true,
16+
"off": false
17+
},
18+
"defaultVariant": "off"
19+
},
20+
"targeting-on": {
21+
"state": "ENABLED",
22+
"variants": {
23+
"on": true,
24+
"off": false
25+
},
26+
"defaultVariant": "off",
27+
"targeting": {
28+
"if": [
29+
{
30+
"===": [
31+
{
32+
"var": "targetingKey"
33+
},
34+
"swift"
35+
]
36+
},
37+
"on"
38+
]
39+
}
40+
},
41+
"static-a-b": {
42+
"state": "ENABLED",
43+
"variants": {
44+
"a": "a",
45+
"b": "b"
46+
},
47+
"defaultVariant": "a"
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)