feat(kotlin-sdk): add ByteLystPlatform unified entry point + 2 new test files (4.2)

New source:
- ByteLystPlatform.kt — unified entry point wiring all services via Context + Config
  (secureStore, client, auth, telemetry, flags, killSwitch, auditLog)
- start(userId?) / stop() lifecycle for telemetry + flags
- Mirrors Swift ByteLystPlatform API

New tests (2 files):
- BLKillSwitchResultTest — ok(), disabled, default, copy
- BLTelemetryEventTest — serialize, deserialize, optional fields
This commit is contained in:
saravanakumardb1 2026-03-19 21:09:24 -07:00
parent 933390e89b
commit 70635ba80e
3 changed files with 211 additions and 0 deletions

View File

@ -0,0 +1,80 @@
package com.bytelyst.platform
import android.content.Context
/**
* Unified entry point for the ByteLyst platform SDK.
*
* Creates and wires all platform services from a single config + context.
* Mirrors the Swift `ByteLystPlatform` API.
*
* Usage:
* ```kotlin
* val platform = ByteLystPlatform(
* context = applicationContext,
* config = BLPlatformConfig(
* productId = "chronomind",
* baseUrl = "https://api.chronomind.app",
* applicationId = "com.chronomind.app"
* )
* )
*
* platform.start()
* platform.telemetry.trackScreen("home")
* val isNew = platform.flags.isEnabled("new_feature")
* platform.stop()
* ```
*/
class ByteLystPlatform(
context: Context,
val config: BLPlatformConfig,
) {
/** Secure storage (EncryptedSharedPreferences). */
val secureStore: BLSecureStore = BLSecureStore(context, config.applicationId)
/** HTTP client shared by services that need a token provider. */
val client: BLPlatformClient = BLPlatformClient(config) {
secureStore.getString("access_token")
}
/** Auth client (login, register, refresh, MFA, etc.). */
val auth: BLAuthClient = BLAuthClient(config, secureStore)
/** Telemetry event tracking + batch flush. */
val telemetry: BLTelemetryClient = BLTelemetryClient(config, context)
/** Feature flag polling. */
val flags: BLFeatureFlagClient = BLFeatureFlagClient(config)
/** Kill switch checker. */
val killSwitch: BLKillSwitchClient = BLKillSwitchClient(config)
/** Local rotating audit log. */
val auditLog: BLAuditLogger = BLAuditLogger(context, config)
/** Whether [start] has been called. */
var isStarted: Boolean = false
private set
/**
* Start all services: telemetry flush timer, feature flag polling.
* Call on app launch / Activity.onCreate.
*/
fun start(userId: String? = null) {
if (isStarted) return
isStarted = true
telemetry.start()
flags.init(userId)
}
/**
* Stop all services: flush telemetry, stop flag polling.
* Call on app background / Activity.onDestroy.
*/
fun stop() {
if (!isStarted) return
isStarted = false
telemetry.stop()
flags.stop()
}
}

View File

@ -0,0 +1,39 @@
package com.bytelyst.platform
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
class BLKillSwitchResultTest {
@Test
fun `ok() should return not-disabled result`() {
val result = BLKillSwitchClient.KillSwitchResult.ok()
assertFalse(result.disabled)
assertNull(result.message)
}
@Test
fun `disabled result should have message`() {
val result = BLKillSwitchClient.KillSwitchResult(
disabled = true,
message = "Under maintenance"
)
assertTrue(result.disabled)
assertEquals("Under maintenance", result.message)
}
@Test
fun `default result should not be disabled`() {
val result = BLKillSwitchClient.KillSwitchResult()
assertFalse(result.disabled)
assertNull(result.message)
}
@Test
fun `data class copy should work`() {
val original = BLKillSwitchClient.KillSwitchResult.ok()
val modified = original.copy(disabled = true, message = "Updating")
assertTrue(modified.disabled)
assertEquals("Updating", modified.message)
}
}

View File

@ -0,0 +1,92 @@
package com.bytelyst.platform
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
class BLTelemetryEventTest {
private val json = Json { ignoreUnknownKeys = true; encodeDefaults = true }
@Test
fun `event should serialize to JSON`() {
val event = BLTelemetryClient.TelemetryEvent(
id = "evt-1",
productId = "testapp",
anonymousInstallId = "inst-1",
sessionId = "sess-1",
platform = "android",
channel = "native",
osFamily = "android",
osVersion = "14",
appVersion = "1.0.0",
buildNumber = "42",
releaseChannel = "beta",
eventType = "info",
module = "test",
eventName = "unit_test",
occurredAt = "2026-01-01T00:00:00Z",
)
val jsonStr = json.encodeToString(event)
assertTrue(jsonStr.contains("\"productId\":\"testapp\""))
assertTrue(jsonStr.contains("\"eventName\":\"unit_test\""))
}
@Test
fun `event should deserialize from JSON`() {
val jsonStr = """
{
"id": "evt-1",
"productId": "testapp",
"anonymousInstallId": "inst-1",
"sessionId": "sess-1",
"platform": "android",
"channel": "native",
"osFamily": "android",
"osVersion": "14",
"appVersion": "1.0.0",
"buildNumber": "42",
"releaseChannel": "beta",
"eventType": "info",
"module": "test",
"eventName": "unit_test",
"occurredAt": "2026-01-01T00:00:00Z"
}
""".trimIndent()
val event = json.decodeFromString<BLTelemetryClient.TelemetryEvent>(jsonStr)
assertEquals("testapp", event.productId)
assertEquals("unit_test", event.eventName)
assertNull(event.feature)
assertNull(event.tags)
}
@Test
fun `event with optional fields should serialize correctly`() {
val event = BLTelemetryClient.TelemetryEvent(
id = "evt-2",
productId = "testapp",
anonymousInstallId = "inst-1",
sessionId = "sess-1",
platform = "android",
channel = "native",
osFamily = "android",
osVersion = "14",
appVersion = "1.0.0",
buildNumber = "42",
releaseChannel = "beta",
eventType = "error",
module = "auth",
eventName = "login_failed",
feature = "social_login",
message = "Token expired",
tags = mapOf("provider" to "google"),
metrics = mapOf("retryCount" to 3.0),
occurredAt = "2026-01-01T00:00:00Z",
)
val jsonStr = json.encodeToString(event)
assertTrue(jsonStr.contains("\"feature\":\"social_login\""))
assertTrue(jsonStr.contains("\"provider\":\"google\""))
assertTrue(jsonStr.contains("\"retryCount\":3.0"))
}
}