Skip to content

Commit

Permalink
Add tests using Micro for payload validation (close #736)
Browse files Browse the repository at this point in the history
  • Loading branch information
matus-tomlein committed Dec 5, 2022
1 parent 1f52548 commit aea245f
Show file tree
Hide file tree
Showing 7 changed files with 442 additions and 15 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/build.yml
Expand Up @@ -59,6 +59,26 @@ jobs:
- name: Checkout
uses: actions/checkout@v3

# -- Micro --
- name: Cache Micro
id: cache-micro
uses: actions/cache@v2
with:
path: micro.jar
key: ${{ runner.os }}-micro

- name: Get micro
if: steps.cache-micro.outputs.cache-hit != 'true'
run: curl -o micro.jar -L https://github.com/snowplow-incubator/snowplow-micro/releases/download/micro-1.3.4/snowplow-micro-1.3.4.jar

- name: Run Micro in background
run: java -jar micro.jar

- name: Wait on Micro endpoint
timeout-minutes: 2
run: while ! nc -z '0.0.0.0' 9090; do sleep 1; done
# -- Micro --

- name: Select Xcode Version
run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode-version }}.app/Contents/Developer

Expand Down
27 changes: 14 additions & 13 deletions Sources/Core/Tracker/StateFuture.swift
Expand Up @@ -27,19 +27,6 @@ import Foundation
/// For this reason, the StateFuture can be the head of StateFuture chain which will collapse once the StateFuture
/// head is asked to get the real state value.
class StateFuture: NSObject {
var state: State? {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
if computedState == nil {
if let stateMachine = stateMachine, let event = event {
computedState = stateMachine.transition(from: event, state: previousState?.state)
}
event = nil
previousState = nil
stateMachine = nil
}
return computedState
}
private var event: Event?
private var previousState: StateFuture?
private var stateMachine: StateMachineProtocol?
Expand All @@ -51,4 +38,18 @@ class StateFuture: NSObject {
self.previousState = previousState
self.stateMachine = stateMachine
}

func computeState() -> State? {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
if computedState == nil {
if let stateMachine = stateMachine, let event = event {
computedState = stateMachine.transition(from: event, state: previousState?.computeState())
previousState = nil
self.event = nil
self.stateMachine = nil
}
}
return computedState
}
}
2 changes: 1 addition & 1 deletion Sources/Core/Tracker/StateManager.swift
Expand Up @@ -100,7 +100,7 @@ class StateManager: NSObject {
externally)
Remove the early state-computation only when these two problems are fixed.
*/
_ = currentStateFuture.state // Early state-computation
_ = currentStateFuture.computeState() // Early state-computation
}
}
return trackerState.snapshot()
Expand Down
2 changes: 1 addition & 1 deletion Sources/Core/Tracker/TrackerState.swift
Expand Up @@ -58,7 +58,7 @@ class TrackerState: NSObject, TrackerStateSnapshot {
// Protocol SPTrackerStateSnapshot

func state(withIdentifier stateIdentifier: String) -> State? {
return stateFuture(withIdentifier: stateIdentifier)?.state
return stateFuture(withIdentifier: stateIdentifier)?.computeState()
}

func state(withStateMachine stateMachine: StateMachineProtocol) -> State? {
Expand Down
5 changes: 5 additions & 0 deletions Sources/Snowplow/Events/SelfDescribing.swift
Expand Up @@ -59,4 +59,9 @@ public class SelfDescribing: SelfDescribingAbstract {
self._schema = schema
self._payload = payload
}

public init(schema: String, payload: [String : String]) {
self._schema = schema
self._payload = payload as [String : NSObject]
}
}
198 changes: 198 additions & 0 deletions Tests/Integration/TestTrackEventsToMicro.swift
@@ -0,0 +1,198 @@
//
// TestWithMicro.swift
// Snowplow-iOSTests
//
// Copyright (c) 2013-2022 Snowplow Analytics Ltd. All rights reserved.
//
// This program is licensed to you under the Apache License Version 2.0,
// and you may not use this file except in compliance with the Apache License
// Version 2.0. You may obtain a copy of the Apache License Version 2.0 at
// http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the Apache License Version 2.0 is distributed on
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the Apache License Version 2.0 for the specific
// language governing permissions and limitations there under.
//
// Authors: Michael Hadam
// License: Apache License Version 2.0
//

import XCTest
import SnowplowTracker

class TestTrackEventsToMicro: XCTestCase {
var tracker: TrackerController?

override func setUp() {
tracker = Snowplow.createTracker(namespace: "ns", network: NetworkConfiguration(endpoint: Micro.endpoint))!

wait(for: [Micro.reset()], timeout: Micro.timeout)
}

func testTrackStructuredEvent() {
let event = Structured(category: "shop", action: "add-to-basket")
event.label = "Add To Basket"
event.property = "pcs"
event.value = 2.0
track(event)

wait(for: [
Micro.expectCounts(good: 1),
Micro.expectPrimitiveEvent() { actual in
XCTAssertEqual("shop", actual.se_category)
XCTAssertEqual("add-to-basket", actual.se_action)
XCTAssertEqual("Add To Basket", actual.se_label)
XCTAssertEqual("pcs", actual.se_property)
XCTAssertEqual(2.0, actual.se_value)
}
], timeout: Micro.timeout)
}

func testTrackSelfDescribing() {
let event = SelfDescribing(
schema: "iglu:com.snowplowanalytics.snowplow/screen_view/jsonschema/1-0-0",
payload: [
"name": "test", "id": "something else"
]
)
track(event)

wait(for: [
Micro.expectCounts(good: 1),
Micro.expectSelfDescribingEvent() { (actual: ScreenViewExpected) in
XCTAssertEqual("test", actual.name)
XCTAssertEqual("something else", actual.id)
}
], timeout: Micro.timeout)
}

func testTrackScreenViews() {
// track the first screen view
track(ScreenView(name: "screen1", screenId: UUID()))
wait(for: [Micro.expectCounts(good: 1)], timeout: Micro.timeout)
wait(for: [Micro.reset()], timeout: Micro.timeout)

// track the second screen view and check reference to previous
track(ScreenView(name: "screen2", screenId: UUID()))
wait(for: [
Micro.expectCounts(good: 1),
Micro.expectSelfDescribingEvent() { (actual: ScreenViewExpected) in
XCTAssertEqual("screen2", actual.name)
XCTAssertEqual("screen1", actual.previousName)
}
], timeout: Micro.timeout)
wait(for: [Micro.reset()], timeout: Micro.timeout)

// track another event and check screen context
track(Timing(category: "cat", variable: "var", timing: 10))
wait(for: [
Micro.expectEventContext(
schema: "iglu:com.snowplowanalytics.mobile/screen/jsonschema/1-0-0"
) { (actual: ScreenContextExpected) in
XCTAssertEqual("screen2", actual.name)
}
], timeout: Micro.timeout)
}

func testTrackDeepLink() {
// track the deep link received event
let deepLink = DeepLinkReceived(url: "https://snowplow.io")
deepLink.referrer = "https://plowsnow.io"
track(deepLink)
wait(for: [
Micro.expectSelfDescribingEvent() { (actual: DeepLinkExpected) in
XCTAssertEqual("https://snowplow.io", actual.url)
XCTAssertEqual("https://plowsnow.io", actual.referrer)
}
], timeout: Micro.timeout)
wait(for: [Micro.reset()], timeout: Micro.timeout)

// track a screen view and check references to the deep link
track(ScreenView(name: "screen", screenId: UUID()))
wait(for: [
// deep link info in payload
Micro.expectPrimitiveEvent() { actual in
XCTAssertEqual("https://snowplow.io", actual.page_url)
XCTAssertEqual("https://plowsnow.io", actual.page_referrer)
},
// deep link info in context entity
Micro.expectEventContext(
schema: "iglu:com.snowplowanalytics.mobile/deep_link/jsonschema/1-0-0"
) { (actual: DeepLinkExpected) in
XCTAssertEqual("https://snowplow.io", actual.url)
XCTAssertEqual("https://plowsnow.io", actual.referrer)
}
], timeout: Micro.timeout)
}

func testSessionTracking() {
// track the first event
track(Structured(category: "cat", action: "act"))
var userId: String?, sessionId: String?
wait(for: [
Micro.expectEventContext(
schema: "iglu:com.snowplowanalytics.snowplow/client_session/jsonschema/1-0-2"
) { (actual: SessionExpected) in
userId = actual.userId
sessionId = actual.sessionId
}
], timeout: Micro.timeout)
wait(for: [Micro.reset()], timeout: Micro.timeout)

// track the second event in the same session
track(Structured(category: "cat", action: "act"))
wait(for: [
Micro.expectEventContext(
schema: "iglu:com.snowplowanalytics.snowplow/client_session/jsonschema/1-0-2"
) { (actual: SessionExpected) in
XCTAssertEqual(userId, actual.userId)
XCTAssertEqual(sessionId, actual.sessionId)
}
], timeout: Micro.timeout)
wait(for: [Micro.reset()], timeout: Micro.timeout)

// start a new session and track event
tracker!.session!.startNewSession()
track(Structured(category: "cat", action: "act"))
wait(for: [
Micro.expectEventContext(
schema: "iglu:com.snowplowanalytics.snowplow/client_session/jsonschema/1-0-2"
) { (actual: SessionExpected) in
XCTAssertEqual(userId, actual.userId)
XCTAssertNotEqual(sessionId, actual.sessionId)
}
], timeout: Micro.timeout)
}

private func track(_ event: Event) {
_ = tracker!.track(event)
tracker!.emitter!.flush()
}
}

struct ScreenViewExpected: Codable {
let name: String
let id: String
let type: String?
let previousName: String?
let previousId: String?
let previousType: String?
let transitionType: String?
}

struct ScreenContextExpected: Codable {
let name: String
let id: String
}

struct DeepLinkExpected: Codable {
let url: String
let referrer: String?
}

struct SessionExpected: Codable {
let sessionId: String
let userId: String
}

0 comments on commit aea245f

Please sign in to comment.