Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/sdk-compliance-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: SDK Compliance Tests

on:
push:
branches: [ main, master ]
paths:
- 'posthog/**'
- 'sdk_compliance_adapter/**'
- '.github/workflows/sdk-compliance-tests.yml'
pull_request:
branches: [ main, master ]
Comment on lines +5 to +11
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
branches: [ main, master ]
paths:
- 'posthog/**'
- 'sdk_compliance_adapter/**'
- '.github/workflows/sdk-compliance-tests.yml'
pull_request:
branches: [ main, master ]
branches: [ main ]
paths:
- 'posthog/**'
- 'sdk_compliance_adapter/**'
- '.github/workflows/sdk-compliance-tests.yml'
pull_request:
branches: [ main ]

we dont have master

paths:
- 'posthog/**'
- 'sdk_compliance_adapter/**'
- '.github/workflows/sdk-compliance-tests.yml'
workflow_dispatch:

permissions:
contents: read
packages: read
pull-requests: write

jobs:
test-android-sdk:
uses: PostHog/posthog-sdk-test-harness/.github/workflows/test-sdk-action.yml@main
with:
adapter-dockerfile: sdk_compliance_adapter/Dockerfile
adapter-context: .
test-harness-version: latest
report-name: android-sdk-compliance-report
7 changes: 7 additions & 0 deletions posthog/api/posthog.api
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public class com/posthog/PostHogConfig {
public final fun getFlushIntervalSeconds ()I
public final fun getGetAnonymousId ()Lkotlin/jvm/functions/Function1;
public final fun getHost ()Ljava/lang/String;
public final fun getHttpClient ()Lokhttp3/OkHttpClient;
public final fun getIntegrations ()Ljava/util/List;
public final fun getLegacyStoragePrefix ()Ljava/lang/String;
public final fun getLogger ()Lcom/posthog/internal/PostHogLogger;
Expand Down Expand Up @@ -156,6 +157,7 @@ public class com/posthog/PostHogConfig {
public final fun setFlushAt (I)V
public final fun setFlushIntervalSeconds (I)V
public final fun setGetAnonymousId (Lkotlin/jvm/functions/Function1;)V
public final fun setHttpClient (Lokhttp3/OkHttpClient;)V
public final fun setLegacyStoragePrefix (Ljava/lang/String;)V
public final fun setLogger (Lcom/posthog/internal/PostHogLogger;)V
public final fun setMaxBatchSize (I)V
Expand Down Expand Up @@ -532,6 +534,11 @@ public final class com/posthog/internal/FlagProperty {
public fun toString ()Ljava/lang/String;
}

public final class com/posthog/internal/GzipRequestInterceptor : okhttp3/Interceptor {
public fun <init> (Lcom/posthog/PostHogConfig;)V
public fun intercept (Lokhttp3/Interceptor$Chain;)Lokhttp3/Response;
}

public final class com/posthog/internal/LocalEvaluationApiResponse {
public static final field Companion Lcom/posthog/internal/LocalEvaluationApiResponse$Companion;
public synthetic fun <init> (Lcom/posthog/internal/LocalEvaluationResponse;Ljava/lang/String;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V
Expand Down
15 changes: 15 additions & 0 deletions posthog/src/main/java/com/posthog/PostHogConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,21 @@ public open class PostHogConfig(
*/
public var releaseIdentifier: String? = null,
) {
/**
* Optional custom OkHttpClient for HTTP requests.
*
* When set, the SDK will use this client instead of creating its own.
* The provided client should be configured with any necessary interceptors,
* timeouts, and other settings required for your use case.
*
* Note: If both `proxy` and `httpClient` are set, the `httpClient` takes precedence
* and the `proxy` setting will be ignored.
*
* Default: `null` (SDK creates its own client).
*/
@PostHogInternal
public var httpClient: okhttp3.OkHttpClient? = null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public var httpClient: okhttp3.OkHttpClient? = null
public var httpClient: OkHttpClient? = null

then just import okhttp3.OkHttpClient


@PostHogInternal
public var logger: PostHogLogger = PostHogNoOpLogger()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import java.io.IOException
* This interceptor compresses the HTTP request body. Many webservers can't handle this!
* @property config The Config
*/
internal class GzipRequestInterceptor(private val config: PostHogConfig) : Interceptor {
public class GzipRequestInterceptor(private val config: PostHogConfig) : Interceptor {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public class GzipRequestInterceptor(private val config: PostHogConfig) : Interceptor {
internal class GzipRequestInterceptor(private val config: PostHogConfig) : Interceptor {

we can revert this i assume? and run make api again

@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
Expand Down
2 changes: 1 addition & 1 deletion posthog/src/main/java/com/posthog/internal/PostHogApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class PostHogApi(
}

private val client: OkHttpClient =
OkHttpClient.Builder()
config.httpClient ?: OkHttpClient.Builder()
.proxy(config.proxy)
.addInterceptor(GzipRequestInterceptor(config))
.build()
Expand Down
21 changes: 21 additions & 0 deletions sdk_compliance_adapter/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
FROM --platform=linux/amd64 gradle:8.5-jdk17 AS builder

WORKDIR /app

COPY . .

RUN echo 'include(":sdk_compliance_adapter")' >> settings.gradle.kts && \
sed -i '/^dependencyResolutionManagement/i plugins {\n id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"\n}' settings.gradle.kts && \
mkdir -p /root/.gradle && \
echo 'org.gradle.java.installations.auto-download=true' > /root/.gradle/gradle.properties && \
gradle :sdk_compliance_adapter:installDist --no-daemon

FROM eclipse-temurin:11-jre

WORKDIR /app

COPY --from=builder /app/sdk_compliance_adapter/build/install/sdk_compliance_adapter /app

EXPOSE 8080

CMD ["/app/bin/sdk_compliance_adapter"]
162 changes: 162 additions & 0 deletions sdk_compliance_adapter/IMPLEMENTATION_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# PostHog Android SDK Compliance Adapter - Implementation Notes

## Overview

This directory contains the compliance test adapter for the PostHog Android SDK. The adapter wraps the SDK and exposes a standardized HTTP API for automated compliance testing.

## Architecture

### Components

1. **adapter.kt** - Ktor-based HTTP server that implements the compliance adapter API
2. **TrackingInterceptor** - OkHttp interceptor that monitors HTTP requests made by the SDK
3. **AdapterState** - Tracks captured events, sent events, retries, and request metadata
4. **Docker** - Containerized build and runtime environment

### Key Implementation Details

#### HTTP Request Tracking

The adapter uses a custom OkHttpClient with a `TrackingInterceptor` that:
- Intercepts all HTTP requests to the `/batch/` endpoint
- Parses request bodies to extract event UUIDs using regex
- Tracks request count, status codes, retry attempts, and event counts
- Updates adapter state with request metadata

#### Event UUID Tracking

Events are tracked using two mechanisms:
1. **beforeSend hook** - Captures UUIDs as events are queued
2. **HTTP interceptor** - Extracts UUIDs from outgoing HTTP requests

This dual approach ensures UUIDs are available immediately when events are captured AND verified when actually sent.

#### SDK Configuration

The adapter configures the PostHog SDK for optimal testing:
- `flushAt = 1` - Send events immediately (or as configured)
- `flushIntervalSeconds` - Fast flush intervals for tests
- `debug = true` - Enable logging
- `httpClient` - Custom OkHttpClient with tracking interceptor

## SDK Modifications Required

To enable HTTP request tracking, the following changes were made to the core SDK:

### PostHogConfig.kt

Added optional `httpClient` parameter:

```kotlin
public var httpClient: okhttp3.OkHttpClient? = null
```

This allows test adapters to inject a custom OkHttpClient with interceptors.

### PostHogApi.kt

Modified to use injected client if provided:

```kotlin
private val client: OkHttpClient =
config.httpClient ?: OkHttpClient.Builder()
.proxy(config.proxy)
.addInterceptor(GzipRequestInterceptor(config))
.build()
```

**These changes are backward compatible** - existing code works unchanged.

## Building

### Local Build (requires Java 8, 11, and 17)

```bash
./gradlew :sdk_compliance_adapter:build
```

### Docker Build (recommended)

```bash
docker build -f sdk_compliance_adapter/Dockerfile -t posthog-android-adapter .
```

The Dockerfile uses Gradle toolchain auto-download to fetch required Java versions.

## Running Tests

### With Docker Compose

```bash
cd sdk_compliance_adapter
docker-compose up --build --abort-on-container-exit
```

This runs:
- **test-harness** - Compliance test runner
- **adapter** - This SDK adapter
- **mock-server** - Mock PostHog server

### SDK Type

The Android SDK uses **server SDK format**:
- Endpoint: `/batch/`
- Format: `{api_key: "...", batch: [{event}, {event}], sent_at: "..."}`

Tests run with `--sdk-type server` flag.

## API Endpoints

The adapter implements the standard compliance adapter API:

- `GET /health` - Health check with SDK version info
- `POST /init` - Initialize SDK with config
- `POST /capture` - Capture a single event
- `POST /flush` - Force flush all pending events
- `GET /state` - Get adapter state for assertions
- `POST /reset` - Reset SDK and adapter state

See [test-harness CONTRACT.yaml](https://github.com/PostHog/posthog-sdk-test-harness/blob/main/CONTRACT.yaml) for full API spec.

## Testing Philosophy

The adapter tests the **core PostHog SDK** (`:posthog` module) which contains:
- All HTTP communication logic
- Retry behavior with exponential backoff
- Event batching and queueing
- Error handling

The `:posthog-android` module is a thin wrapper that adds Android-specific features (lifecycle tracking, etc.) but doesn't change the core compliance behavior.

## Known Limitations

### Java 8 on ARM64

Java 8 is not available for ARM64 (Apple Silicon). The project requires Java 8 for the core module. Solutions:

1. **Docker** (recommended) - Uses Gradle toolchain auto-download
2. **CI/CD** - GitHub Actions provides Java 8 for Linux x64
3. **Modify core** - Upgrade to Java 11 (not recommended - breaks compatibility)

### Flush Timing

The `/flush` endpoint includes a 2-second wait to account for:
- SDK's internal flush timer
- Network latency in Docker environment
- Mock server processing time

This may need adjustment based on test results.

## Future Improvements

1. **Reduce flush wait time** - Profile actual flush timing and optimize
2. **Add compression support** - Currently the adapter doesn't test gzip compression
3. **More detailed error tracking** - Capture and report SDK errors in state
4. **Performance metrics** - Track request timing, payload sizes

## References

- [Test Harness Repository](https://github.com/PostHog/posthog-sdk-test-harness)
- [Browser SDK Adapter](../../posthog-js/packages/browser/sdk_compliance_adapter/) - Reference implementation
- [Adapter Guide](https://github.com/PostHog/posthog-sdk-test-harness/blob/main/ADAPTER_GUIDE.md)
- [Contract Specification](https://github.com/PostHog/posthog-sdk-test-harness/blob/main/CONTRACT.yaml)
97 changes: 97 additions & 0 deletions sdk_compliance_adapter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# PostHog Android SDK Compliance Adapter

This compliance adapter wraps the PostHog Android SDK and exposes a standardized HTTP API for automated compliance testing using the [PostHog SDK Test Harness](https://github.com/PostHog/posthog-sdk-test-harness).

## Quick Start

### Running Tests in CI (Recommended)

Tests run automatically in GitHub Actions on:
- Push to `main`/`master` branch
- Pull requests
- Manual trigger via `workflow_dispatch`

See `.github/workflows/sdk-compliance-tests.yml`

### Running Tests Locally

**Note:** Requires x86_64 architecture due to Java 8 dependency. On Apple Silicon, Docker will use emulation (slower but works).

```bash
cd sdk_compliance_adapter
docker-compose up --build --abort-on-container-exit
```

## Architecture

- **adapter.kt** - Ktor HTTP server implementing the compliance adapter API
- **TrackingInterceptor** - OkHttp interceptor for monitoring SDK HTTP requests
- **Dockerfile** - Multi-stage Docker build (requires x86_64 for Java 8)
- **docker-compose.yml** - Local test orchestration

## SDK Modifications

To enable request tracking, we added an optional `httpClient` parameter to `PostHogConfig`:

```kotlin
// PostHogConfig.kt
public var httpClient: okhttp3.OkHttpClient? = null
```

This allows the test adapter to inject a custom OkHttpClient with tracking interceptors. **This change is fully backward compatible** - existing code works unchanged.

## Implementation Details

### HTTP Request Tracking

The adapter injects a custom OkHttpClient that:
- Intercepts all `/batch/` requests
- Extracts event UUIDs from request bodies
- Tracks status codes, retry attempts, and event counts

### Event Tracking

Events are tracked via:
1. `beforeSend` hook - Captures UUIDs as events are queued
2. HTTP interceptor - Verifies UUIDs in outgoing requests

### SDK Type

The Android SDK uses **server SDK format**:
- Endpoint: `/batch/`
- Format: `{api_key, batch, sent_at}`

Tests run with `--sdk-type server`.

## Files Created

```
sdk_compliance_adapter/
├── adapter.kt # Main adapter implementation
├── build.gradle.kts # Gradle build configuration
├── Dockerfile # Docker build (x86_64)
├── docker-compose.yml # Local test setup
├── README.md # This file
└── IMPLEMENTATION_NOTES.md # Detailed technical notes
```

## Changes to Core SDK

### posthog/src/main/java/com/posthog/PostHogConfig.kt
- Added `httpClient: OkHttpClient?` parameter

### posthog/src/main/java/com/posthog/internal/PostHogApi.kt
- Modified to use injected `httpClient` if provided

### settings.gradle.kts
- Added `:sdk_compliance_adapter` module

### .github/workflows/sdk-compliance-tests.yml
- GitHub Actions workflow for automated testing

## References

- [Test Harness Repository](https://github.com/PostHog/posthog-sdk-test-harness)
- [Adapter Guide](https://github.com/PostHog/posthog-sdk-test-harness/blob/main/ADAPTER_GUIDE.md)
- [Contract Specification](https://github.com/PostHog/posthog-sdk-test-harness/blob/main/CONTRACT.yaml)
- [Browser SDK Adapter](https://github.com/PostHog/posthog-js/tree/main/packages/browser/sdk_compliance_adapter) (Reference)
Loading
Loading