Skip to content
Draft
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 ]
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
13 changes: 13 additions & 0 deletions posthog/src/main/java/com/posthog/PostHogConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,19 @@ public open class PostHogConfig(
* Default: `null` (no proxy).
*/
public var proxy: Proxy? = 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).
*/
public var httpClient: okhttp3.OkHttpClient? = null,
/**
* Configuration for PostHog Surveys feature. (Intended for internal use only)
*
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 {
@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)
45 changes: 45 additions & 0 deletions sdk_compliance_adapter/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
plugins {
kotlin("jvm")
application
}

group = "com.posthog.compliance"
version = "1.0.0"

dependencies {
// PostHog Core SDK
implementation(project(":posthog"))

// Ktor server
val ktorVersion = "2.3.7"
implementation("io.ktor:ktor-server-core:$ktorVersion")
implementation("io.ktor:ktor-server-netty:$ktorVersion")
implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-serialization-gson:$ktorVersion")

// Logging
implementation("ch.qos.logback:logback-classic:1.4.14")

// OkHttp (for interceptor)
implementation("com.squareup.okhttp3:okhttp:4.12.0")
}

application {
mainClass.set("com.posthog.compliance.AdapterKt")
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = "11"
}
}

java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}

// Disable API validation for test adapter
tasks.matching { it.name == "apiCheck" || it.name == "apiDump" }.configureEach {
enabled = false
}
Loading
Loading