Skip to content

Commit f63d449

Browse files
authored
[sc-142474] Contract test service support (#44)
1 parent fe12c9c commit f63d449

File tree

16 files changed

+355
-48
lines changed

16 files changed

+355
-48
lines changed

.circleci/config.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ jobs:
7878
command: swift test -v 2>&1 | tee 'artifacts/raw-logs-swiftpm.txt' | xcpretty -r junit -o 'test-results/swiftpm/junit.xml'
7979
when: always
8080

81+
- run:
82+
name: Run contract tests
83+
command: make contract-tests
84+
8185
- when:
8286
condition: <<parameters.build-doc>>
8387
steps:

.github/pull_request_template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
**Requirements**
22

33
- [ ] I have added test coverage for new or changed functionality
4-
- [ ] I have followed the repository's [pull request submission guidelines](../blob/master/CONTRIBUTING.md#submitting-pull-requests)
4+
- [ ] I have followed the repository's [pull request submission guidelines](../blob/main/CONTRIBUTING.md#submitting-pull-requests)
55
- [ ] I have validated my changes against all supported platform versions
66

77
**Related issues**

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ All notable changes to the LaunchDarkly Swift EventSource library will be docume
3535
## [1.1.0] - 2020-07-20
3636
### Added
3737
- Support `arm64e` on `appletvos`, `iphoneos`, and `macosx` SDKs by extending valid architectures.
38-
- Support for building LDSwiftEventSource on Linux. Currently this library will not generate log messages on Linux, and may not behave correctly on Linux due to Foundation being [incomplete](https://github.com/apple/swift-corelibs-foundation/blob/master/Docs/Status.md).
38+
- Support for building LDSwiftEventSource on Linux. Currently this library will not generate log messages on Linux, and may not behave correctly on Linux due to Foundation being [incomplete](https://github.com/apple/swift-corelibs-foundation/blob/main/Docs/Status.md).
3939

4040
## [1.0.0] - 2020-07-16
4141
This is the first public release of the LDSwiftEventSource library. The following notes are what changed since the previous pre-release version.

CONTRIBUTING.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,15 @@ Or in XCode, simply select the desired target and select `Product -> Test`.
3030

3131
For building on the command line with `xcodebuild`, see the [continuous integration build configuration][ci-config] for examples on building and running tests.
3232

33+
### Running contract tests
34+
35+
To run the standardized contract tests that are run against all LaunchDarkly SSE client implementations:
36+
```
37+
make contract-tests
38+
```
39+
3340
### Generating API documentation
3441

35-
Docs are built with [jazzy](https://github.com/realm/jazzy), which is configured [here](https://github.com/launchdarkly/swift-eventsource/blob/master/.jazzy.yaml). To build them, simply run `jazzy`. Pull requests should keep our documentation coverage at 100%.
42+
Docs are built with [jazzy](https://github.com/realm/jazzy), which is configured [here](https://github.com/launchdarkly/swift-eventsource/blob/main/.jazzy.yaml). To build them, simply run `jazzy`. Pull requests should keep our documentation coverage at 100%.
3643

37-
[ci-config]: https://github.com/launchdarkly/swift-eventsource/blob/master/.circleci/config.yml
44+
[ci-config]: https://github.com/launchdarkly/swift-eventsource/blob/main/.circleci/config.yml

ContractTestService/.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
/*.xcodeproj
5+
xcuserdata/
6+
DerivedData/
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

ContractTestService/Package.resolved

Lines changed: 88 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ContractTestService/Package.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// swift-tools-version:5.0
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "ContractTestService",
7+
platforms: [
8+
.iOS(.v10),
9+
.macOS(.v10_12),
10+
.watchOS(.v3),
11+
.tvOS(.v10),
12+
],
13+
products: [
14+
.executable(
15+
name: "contract-test-service",
16+
targets: ["ContractTestService"]
17+
)
18+
],
19+
dependencies: [
20+
// Local dependency to LDSwiftEventSource
21+
.package(path: ".."),
22+
.package(url: "https://github.com/Kitura/Kitura", from: "2.9.200")
23+
],
24+
targets: [
25+
.target(
26+
name: "ContractTestService",
27+
dependencies: [
28+
"LDSwiftEventSource",
29+
"Kitura"
30+
]
31+
)
32+
]
33+
)

ContractTestService/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# SSE client contract test service
2+
3+
This directory contains an implementation of the cross-platform SSE testing protocol defined by https://github.com/launchdarkly/sse-contract-tests. See that project's `README` for details of this protocol, and the kinds of SSE client capabilities that are relevant to the contract tests. This code should not need to be updated unless the SSE client has added or removed such capabilities.
4+
5+
To run these tests locally, run `make contract-tests` from the project root directory. This downloads the correct version of the test harness tool automatically.
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import Dispatch
2+
import Foundation
3+
import Kitura
4+
import LDSwiftEventSource
5+
6+
struct StatusResp: Encodable {
7+
let name = "swift-eventsource"
8+
let capabilities = ["comments", "headers", "last-event-id", "post", "read-timeout", "report"]
9+
}
10+
11+
struct CreateStreamReq: Decodable {
12+
let streamUrl: URL
13+
let callbackUrl: URL
14+
let initialDelayMs: Int?
15+
let readTimeoutMs: Int?
16+
let lastEventId: String?
17+
let headers: [String: String]?
18+
let method: String?
19+
let body: String?
20+
21+
func createEventSourceConfig() -> EventSource.Config {
22+
var esConfig = EventSource.Config(handler: CallbackHandler(baseUrl: callbackUrl), url: streamUrl)
23+
if let initialDelayMs = initialDelayMs { esConfig.reconnectTime = Double(initialDelayMs) / 1000.0 }
24+
if let readTimeoutMs = readTimeoutMs { esConfig.idleTimeout = Double(readTimeoutMs) / 1000.0 }
25+
if let lastEventId = lastEventId { esConfig.lastEventId = lastEventId }
26+
if let headers = headers { esConfig.headers = headers }
27+
if let method = method { esConfig.method = method }
28+
if let body = body { esConfig.body = Data(body.utf8) }
29+
return esConfig
30+
}
31+
}
32+
33+
class CallbackHandler: EventHandler {
34+
struct EventPayloadEvent: Encodable {
35+
let type: String
36+
let data: String
37+
let id: String?
38+
}
39+
40+
struct EventPayload: Encodable {
41+
let kind = "event"
42+
let event: EventPayloadEvent
43+
}
44+
45+
struct CommentPayload: Encodable {
46+
let kind = "comment"
47+
let comment: String
48+
}
49+
50+
struct ErrorPayload: Encodable {
51+
let kind = "error"
52+
}
53+
54+
let baseUrl: URL
55+
var count = 0
56+
57+
init(baseUrl: URL) {
58+
self.baseUrl = baseUrl
59+
}
60+
61+
func onOpened() { }
62+
func onClosed() { }
63+
64+
func sendUpdate<T: Encodable>(_ update: T) {
65+
count += 1
66+
var request = URLRequest(url: baseUrl.appendingPathComponent(String(count), isDirectory: false))
67+
request.httpMethod = "POST"
68+
let data = try! JSONEncoder().encode(update)
69+
URLSession.shared.uploadTask(with: request, from: data) { _, _, _ in }.resume()
70+
}
71+
72+
func onMessage(eventType type: String, messageEvent msg: MessageEvent) {
73+
sendUpdate(EventPayload(event: EventPayloadEvent(type: type, data: msg.data, id: msg.lastEventId)))
74+
}
75+
76+
func onComment(comment: String) {
77+
sendUpdate(CommentPayload(comment: comment))
78+
}
79+
80+
func onError(error: Error) {
81+
sendUpdate(ErrorPayload())
82+
}
83+
}
84+
85+
let stateQueue = DispatchQueue(label: "StateQueue")
86+
var nextId: Int = 0
87+
var state: [String: EventSource] = [:]
88+
89+
let router = Router()
90+
91+
router.get("/") { _, resp, next in
92+
resp.send(StatusResp())
93+
next()
94+
}
95+
96+
router.delete("/") { _, resp, next in
97+
resp.send(["message": "Shutting down contract test service"])
98+
next()
99+
Kitura.stop()
100+
}
101+
102+
router.post("/") { req, resp, next in
103+
guard let createStreamReq = try? req.read(as: CreateStreamReq.self)
104+
else {
105+
resp.status(.badRequest).send(["message": "Body of POST to '/' invalid"])
106+
return next()
107+
}
108+
let es = EventSource(config: createStreamReq.createEventSourceConfig())
109+
let location: String = stateQueue.sync {
110+
state[String(nextId)] = es
111+
nextId += 1
112+
return "/control/\(nextId - 1)"
113+
}
114+
es.start()
115+
resp.headers["Location"] = location
116+
resp.send(["message": "Created test service entity at \(location)"])
117+
next()
118+
}
119+
120+
router.delete("/control/:id") { req, resp, next in
121+
stateQueue.sync {
122+
if let es = state.removeValue(forKey: req.parameters["id"]!) {
123+
es.stop()
124+
resp.send(["message": "Shut down test service entity at \(req.matchedPath)"])
125+
} else {
126+
resp.status(.notFound).send(["message": "Test service entity not found at \(req.matchedPath)"])
127+
}
128+
}
129+
next()
130+
}
131+
132+
Kitura.addHTTPServer(onPort: 8000, onAddress: "localhost", with: router)
133+
Kitura.run()

Makefile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
build:
2+
swift build
3+
4+
clean:
5+
swift clean
6+
7+
test:
8+
swift test
9+
10+
TEMP_TEST_OUTPUT=/tmp/sse-contract-test-service.log
11+
12+
build-contract-tests:
13+
cd ContractTestService && swift build
14+
15+
start-contract-test-service:
16+
./ContractTestService/.build/debug/contract-test-service
17+
18+
start-contract-test-service-bg:
19+
echo "Test service output will be captured in $(TEMP_TEST_OUTPUT)"
20+
make start-contract-test-service >$(TEMP_TEST_OUTPUT) 2>&1 &
21+
22+
run-contract-tests:
23+
curl -s https://raw.githubusercontent.com/launchdarkly/sse-contract-tests/v2.0.0/downloader/run.sh \
24+
| VERSION=v2 PARAMS="-url http://localhost:8000 -debug -stop-service-at-end" sh
25+
26+
contract-tests: build-contract-tests start-contract-test-service-bg run-contract-tests
27+
28+
.PHONY: build clean test build-contract-tests start-contract-test-service run-contract-tests contract-tests

0 commit comments

Comments
 (0)