Skip to content

Commit 902a423

Browse files
committed
tests
1 parent d909a61 commit 902a423

File tree

9 files changed

+238
-8
lines changed

9 files changed

+238
-8
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## Next
22

3+
- feat: use remote config API ([#233](https://github.com/PostHog/posthog-android/pull/233))
4+
35
## 3.12.0 - 2025-03-10
46

57
- feat: support reuse of `anonymousId` between user changes ([#229](https://github.com/PostHog/posthog-android/pull/229))

posthog/api/posthog.api

+4
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,11 @@ public final class com/posthog/PostHog$Companion : com/posthog/PostHogInterface
7171

7272
public class com/posthog/PostHogConfig {
7373
public static final field Companion Lcom/posthog/PostHogConfig$Companion;
74+
public static final field DEFAULT_EU_ASSETS_HOST Ljava/lang/String;
75+
public static final field DEFAULT_EU_HOST Ljava/lang/String;
7476
public static final field DEFAULT_HOST Ljava/lang/String;
77+
public static final field DEFAULT_US_ASSETS_HOST Ljava/lang/String;
78+
public static final field DEFAULT_US_HOST Ljava/lang/String;
7579
public fun <init> (Ljava/lang/String;Ljava/lang/String;ZZZZZIIIILcom/posthog/PostHogEncryption;Lcom/posthog/PostHogOnFeatureFlags;ZLcom/posthog/PostHogPropertiesSanitizer;Lkotlin/jvm/functions/Function1;ZLcom/posthog/PersonProfiles;)V
7680
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ZZZZZIIIILcom/posthog/PostHogEncryption;Lcom/posthog/PostHogOnFeatureFlags;ZLcom/posthog/PostHogPropertiesSanitizer;Lkotlin/jvm/functions/Function1;ZLcom/posthog/PersonProfiles;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
7781
public final fun addIntegration (Lcom/posthog/PostHogIntegration;)V

posthog/src/main/java/com/posthog/PostHogConfig.kt

+8-1
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,13 @@ public open class PostHogConfig(
218218
}
219219

220220
public companion object {
221-
public const val DEFAULT_HOST: String = "https://us.i.posthog.com"
221+
public const val DEFAULT_US_HOST: String = "https://us.i.posthog.com"
222+
public const val DEFAULT_US_ASSETS_HOST: String = "https://us-assets.i.posthog.com"
223+
224+
// flutter uses it
225+
public const val DEFAULT_HOST: String = DEFAULT_US_HOST
226+
227+
public const val DEFAULT_EU_HOST: String = "https://eu.i.posthog.com"
228+
public const val DEFAULT_EU_ASSETS_HOST: String = "https://eu-assets.i.posthog.com"
222229
}
223230
}

posthog/src/main/java/com/posthog/internal/PostHogApi.kt

+15-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package com.posthog.internal
22

33
import com.posthog.PostHogConfig
4+
import com.posthog.PostHogConfig.Companion.DEFAULT_EU_ASSETS_HOST
5+
import com.posthog.PostHogConfig.Companion.DEFAULT_EU_HOST
6+
import com.posthog.PostHogConfig.Companion.DEFAULT_US_ASSETS_HOST
7+
import com.posthog.PostHogConfig.Companion.DEFAULT_US_HOST
48
import com.posthog.PostHogEvent
59
import okhttp3.MediaType.Companion.toMediaType
610
import okhttp3.OkHttpClient
@@ -17,10 +21,14 @@ import java.io.OutputStream
1721
internal class PostHogApi(
1822
private val config: PostHogConfig,
1923
) {
24+
private companion object {
25+
private const val APP_JSON_UTF_8 = "application/json; charset=utf-8"
26+
}
27+
2028
private val mediaType by lazy {
2129
try {
2230
// can throw IllegalArgumentException
23-
"application/json; charset=utf-8".toMediaType()
31+
APP_JSON_UTF_8.toMediaType()
2432
} catch (ignored: Throwable) {
2533
null
2634
}
@@ -119,15 +127,15 @@ internal class PostHogApi(
119127
}
120128

121129
@Throws(PostHogApiError::class, IOException::class)
122-
fun loadRemoteConfig(): PostHogRemoteConfigResponse? {
130+
fun remoteConfig(): PostHogRemoteConfigResponse? {
123131
var host = theHost
124132
host =
125133
when (host) {
126-
"https://us.i.posthog.com" -> {
127-
"https://us-assets.i.posthog.com"
134+
DEFAULT_US_HOST -> {
135+
DEFAULT_US_ASSETS_HOST
128136
}
129-
"https://eu.i.posthog.com" -> {
130-
"https://eu-assets.i.posthog.com"
137+
DEFAULT_EU_HOST -> {
138+
DEFAULT_EU_ASSETS_HOST
131139
}
132140
else -> {
133141
host
@@ -138,6 +146,7 @@ internal class PostHogApi(
138146
Request.Builder()
139147
.url("$host/array/${config.apiKey}/config")
140148
.header("User-Agent", config.userAgent)
149+
.header("Content-Type", APP_JSON_UTF_8)
141150
.get()
142151
.build()
143152

posthog/src/main/java/com/posthog/internal/PostHogRemoteConfig.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ internal class PostHogRemoteConfig(
104104
}
105105

106106
try {
107-
val response = api.loadRemoteConfig()
107+
val response = api.remoteConfig()
108108

109109
response?.let {
110110
synchronized(remoteConfigLock) {

posthog/src/test/java/com/posthog/PostHogTest.kt

+77
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,83 @@ internal class PostHogTest {
193193
sut.close()
194194
}
195195

196+
@Test
197+
fun `preload remote config if enabled`() {
198+
val http = mockHttp()
199+
val url = http.url("/")
200+
201+
val sut = getSut(url.toString(), remoteConfig = true, preloadFeatureFlags = false)
202+
203+
remoteConfigExecutor.shutdownAndAwaitTermination()
204+
205+
val request = http.takeRequest()
206+
assertEquals(1, http.requestCount)
207+
assertEquals("/array/${API_KEY}/config", request.path)
208+
209+
sut.close()
210+
}
211+
212+
@Test
213+
fun `preload remote config and flags if enabled`() {
214+
val file = File("src/test/resources/json/basic-remote-config.json")
215+
val responseText = file.readText()
216+
217+
val http =
218+
mockHttp(
219+
response =
220+
MockResponse()
221+
.setBody(responseText),
222+
)
223+
http.enqueue(
224+
MockResponse()
225+
.setBody(responseDecideApi),
226+
)
227+
val url = http.url("/")
228+
229+
val sut = getSut(url.toString(), remoteConfig = true)
230+
231+
remoteConfigExecutor.shutdownAndAwaitTermination()
232+
233+
val remoteConfigRequest = http.takeRequest()
234+
235+
assertEquals(2, http.requestCount)
236+
assertEquals("/array/${API_KEY}/config", remoteConfigRequest.path)
237+
238+
val decideApiRequest = http.takeRequest()
239+
assertEquals("/decide/?v=3", decideApiRequest.path)
240+
241+
sut.close()
242+
}
243+
244+
@Test
245+
fun `preload remote config but no flags`() {
246+
val file = File("src/test/resources/json/basic-remote-config-no-flags.json")
247+
val responseText = file.readText()
248+
249+
val http =
250+
mockHttp(
251+
response =
252+
MockResponse()
253+
.setBody(responseText),
254+
)
255+
http.enqueue(
256+
MockResponse()
257+
.setBody(responseDecideApi),
258+
)
259+
val url = http.url("/")
260+
261+
val sut = getSut(url.toString(), remoteConfig = true)
262+
263+
remoteConfigExecutor.shutdownAndAwaitTermination()
264+
265+
val remoteConfigRequest = http.takeRequest()
266+
267+
assertEquals(1, http.requestCount)
268+
assertEquals("/array/${API_KEY}/config", remoteConfigRequest.path)
269+
270+
sut.close()
271+
}
272+
196273
@Test
197274
fun `isFeatureEnabled returns value after reloaded`() {
198275
val http =

posthog/src/test/java/com/posthog/internal/PostHogApiTest.kt

+43
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,47 @@ internal class PostHogApiTest {
102102
assertEquals("Client Error", exc.message)
103103
assertNotNull(exc.body)
104104
}
105+
106+
@Test
107+
fun `remote config returns successful response`() {
108+
val file = File("src/test/resources/json/basic-remote-config.json")
109+
val responseApi = file.readText()
110+
111+
val http =
112+
mockHttp(
113+
response =
114+
MockResponse()
115+
.setBody(responseApi),
116+
)
117+
val url = http.url("/")
118+
119+
val sut = getSut(host = url.toString())
120+
121+
val response = sut.remoteConfig()
122+
123+
val request = http.takeRequest()
124+
125+
assertNotNull(response)
126+
assertEquals("posthog-java/${BuildConfig.VERSION_NAME}", request.headers["User-Agent"])
127+
assertEquals("GET", request.method)
128+
assertEquals("/array/${API_KEY}/config", request.path)
129+
assertEquals("gzip", request.headers["Accept-Encoding"])
130+
assertEquals("application/json; charset=utf-8", request.headers["Content-Type"])
131+
}
132+
133+
@Test
134+
fun `remote config throws if not successful`() {
135+
val http = mockHttp(response = MockResponse().setResponseCode(400).setBody("error"))
136+
val url = http.url("/")
137+
138+
val sut = getSut(host = url.toString())
139+
140+
val exc =
141+
assertThrows(PostHogApiError::class.java) {
142+
sut.remoteConfig()
143+
}
144+
assertEquals(400, exc.statusCode)
145+
assertEquals("Client Error", exc.message)
146+
assertNotNull(exc.body)
147+
}
105148
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"token": "phc_QFbR1y41s5sxnNTZoyKG2NJo2RlsCIWkUfdpawgb40D",
3+
"supportedCompression": [
4+
"gzip",
5+
"gzip-js"
6+
],
7+
"hasFeatureFlags": false,
8+
"captureDeadClicks": false,
9+
"capturePerformance": {
10+
"network_timing": true,
11+
"web_vitals": false,
12+
"web_vitals_allowed_metrics": null
13+
},
14+
"autocapture_opt_out": true,
15+
"autocaptureExceptions": false,
16+
"analytics": {
17+
"endpoint": "/i/v0/e/"
18+
},
19+
"elementsChainAsString": true,
20+
"sessionRecording": {
21+
"endpoint": "/s/",
22+
"consoleLogRecordingEnabled": true,
23+
"recorderVersion": "v2",
24+
"sampleRate": null,
25+
"minimumDurationMilliseconds": 1000,
26+
"linkedFlag": null,
27+
"networkPayloadCapture": {
28+
"recordBody": true,
29+
"recordHeaders": true
30+
},
31+
"masking": null,
32+
"urlTriggers": [],
33+
"urlBlocklist": [],
34+
"eventTriggers": [],
35+
"scriptConfig": null,
36+
"recordCanvas": true,
37+
"canvasFps": 3,
38+
"canvasQuality": "0.4"
39+
},
40+
"heatmaps": false,
41+
"surveys": false,
42+
"defaultIdentifiedOnly": true,
43+
"siteApps": []
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"token": "phc_QFbR1y41s5sxnNTZoyKG2NJo2RlsCIWkUfdpawgb40D",
3+
"supportedCompression": [
4+
"gzip",
5+
"gzip-js"
6+
],
7+
"hasFeatureFlags": true,
8+
"captureDeadClicks": false,
9+
"capturePerformance": {
10+
"network_timing": true,
11+
"web_vitals": false,
12+
"web_vitals_allowed_metrics": null
13+
},
14+
"autocapture_opt_out": true,
15+
"autocaptureExceptions": false,
16+
"analytics": {
17+
"endpoint": "/i/v0/e/"
18+
},
19+
"elementsChainAsString": true,
20+
"sessionRecording": {
21+
"endpoint": "/s/",
22+
"consoleLogRecordingEnabled": true,
23+
"recorderVersion": "v2",
24+
"sampleRate": null,
25+
"minimumDurationMilliseconds": 1000,
26+
"linkedFlag": null,
27+
"networkPayloadCapture": {
28+
"recordBody": true,
29+
"recordHeaders": true
30+
},
31+
"masking": null,
32+
"urlTriggers": [],
33+
"urlBlocklist": [],
34+
"eventTriggers": [],
35+
"scriptConfig": null,
36+
"recordCanvas": true,
37+
"canvasFps": 3,
38+
"canvasQuality": "0.4"
39+
},
40+
"heatmaps": false,
41+
"surveys": false,
42+
"defaultIdentifiedOnly": true,
43+
"siteApps": []
44+
}

0 commit comments

Comments
 (0)