Skip to content
Merged
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
34 changes: 34 additions & 0 deletions .cursor/rules/contentstack-android-cda.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
description: Contentstack CDA patterns – Stack/Config, HTTP, retry, callbacks, Content Delivery API
globs: "contentstack/src/main/java/com/contentstack/sdk/**/*.java", "contentstack/src/main/java/com/contentstack/sdk/**/*.kt"
---

# Contentstack Android CDA – SDK Rules

Apply when editing the SDK core (`com.contentstack.sdk`). Keep behavior aligned with the [Content Delivery API](https://www.contentstack.com/docs/apis/content-delivery-api/).

## Stack and Config

- **Entry point:** `Contentstack.stack(Context, apiKey, deliveryToken, environment)` returns a `Stack`. Use `Config` for optional settings (host, version, region, branch, proxy, connection pool).
- **Default host:** `cdn.contentstack.io`; **API version:** `v3` (see `Config`).
- **Config options:** host, version, environment, branch, region (`ContentstackRegion`), proxy, connection pool, endpoint. Set these via `Config` before building the stack.
- **Region/branch:** Support `Config.setRegion()` and `Config.setBranch()` for regional and branch-specific delivery.

## HTTP layer

- **Requests** use **`CSHttpConnection`** (Volley) and/or **Retrofit** + **OkHttp** (e.g. `Stack`, `APIService`). Do not bypass these for CDA calls.
- **Headers:** Use the same headers and User-Agent as the rest of the SDK (see constants and request setup in `CSHttpConnection` and Stack/APIService).
- **Errors:** Map API errors to the SDK **`Error`** class and pass to **`ResultCallBack`** or equivalent callback.

## Retry and resilience

- **Retry:** Volley uses `DefaultRetryPolicy` (e.g. in `CSHttpConnection`); constants in `SDKConstant` (e.g. `TimeOutDuration`, `NumRetry`, `BackOFMultiplier`). Keep retry/timeout behavior consistent when changing the HTTP layer.

## Callbacks and async

- Use existing callback types (e.g. **`ResultCallBack`**, **`EntryResultCallBack`**, **`QueryResultsCallBack`**) for async results. Do not introduce incompatible callback signatures without considering backward compatibility.
- Pass **`Error`** and response data through the same callback patterns used elsewhere in the SDK.

## CDA concepts

- **Entry,** **Query,** **Asset,** **Content Type,** **Sync** – follow existing class and method names and the CDA API semantics (query params, response parsing). When adding new CDA features, align with the official Content Delivery API documentation.
27 changes: 27 additions & 0 deletions .cursor/rules/dev-workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Development Workflow – Contentstack Android CDA SDK

Use this as the standard workflow when contributing to the Android CDA SDK.

## Branches

- Use feature branches for changes (e.g. `feat/...`, `fix/...`).
- Base work off the appropriate long-lived branch (e.g. `staging`, `development`) per team norms.

## Running tests

- **Unit tests:** `./gradlew :contentstack:testDebugUnitTest`
- **Instrumented / connected tests:** `./gradlew :contentstack:connectedDebugAndroidTest` (device or emulator required)
- **Full test pass:** `./gradlew :contentstack:testDebugUnitTest :contentstack:connectedDebugAndroidTest`
- **Coverage report:** `./gradlew :contentstack:jacocoTestReport`

Run unit tests before opening a PR. Instrumented tests may require `local.properties` with stack credentials (see `contentstack/build.gradle` buildConfigField usage).

## Pull requests

- Ensure the build passes: `./gradlew :contentstack:assembleDebug :contentstack:testDebugUnitTest`
- Follow the **code-review** rule (`.cursor/rules/code-review.mdc`) for the PR checklist.
- Keep changes backward-compatible for public API; call out any breaking changes clearly.

## Optional: TDD

If the team uses TDD, follow RED–GREEN–REFACTOR when adding behavior. The **testing** rule and **skills/testing** skill describe test structure and naming.
41 changes: 41 additions & 0 deletions .cursor/rules/java.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
description: Java/Kotlin style and com.contentstack.sdk conventions for the Android CDA SDK
globs: "**/*.java", "**/*.kt"
---

# Java / Kotlin Standards – Contentstack Android CDA SDK

Apply these conventions when editing Java (or Kotlin) code in this repository.

## Language and runtime

- **Java:** Target **Java 8** compatibility for the library (see `contentstack/build.gradle` compileOptions). Avoid language or API features that require a higher version without updating the module.
- **Kotlin:** If present, follow existing Kotlin style and interop with `com.contentstack.sdk`; prefer null-safety and idiomatic Kotlin where it does not break public API.
- Avoid raw types; use proper generics where applicable.

## Package and layout

- All SDK code lives under **`com.contentstack.sdk`** (see `contentstack/src/main/java/com/contentstack/sdk/`).
- Keep the existing package structure; do not introduce new top-level packages without alignment with the rest of the SDK.

## Naming

- **Classes:** PascalCase (e.g. `CSHttpConnection`, `Config`).
- **Methods/variables:** camelCase.
- **Constants:** UPPER_SNAKE_CASE (e.g. in `SDKConstant`, `ErrorMessages`).
- **Test classes:** `Test*` for unit tests; instrumented tests in `androidTest` (see **testing.mdc**).

## Logging

- Use **Android `Log`** or project logging as in `CSHttpConnection` (TAG-based). Obtain loggers with a class-named TAG.
- Log at appropriate levels (e.g. `Log.w` for recoverable issues, `Log.d` for debug).

## Null-safety and annotations

- Use **`@NonNull`** / **`@Nullable`** (Android or JetBrains) where the project already does, to document nullability for public API.
- Validate or document parameters for public methods where NPEs would be surprising.

## General

- Prefer immutability where practical (e.g. final fields, defensive copies for collections).
- Document public API with Javadoc; keep examples in Javadoc in sync with actual usage (e.g. `Contentstack.stack(...)`, `Config`).
33 changes: 33 additions & 0 deletions .cursor/rules/testing.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
description: JUnit 4, Robolectric, androidTest, test naming, JaCoCo
globs: "contentstack/src/test/**/*.java", "contentstack/src/test/**/*.kt", "contentstack/src/androidTest/**/*.java", "contentstack/src/androidTest/**/*.kt"
---

# Testing Rules – Contentstack Android CDA SDK

Apply when writing or editing tests. The project uses **JUnit 4**, **Robolectric** (unit), **AndroidX Test** / **Espresso** (instrumented), and **JaCoCo** (see `contentstack/build.gradle`).

## Test naming and layout

- **Unit tests:** Class name prefix **`Test`** (e.g. `TestEntry`, `TestStack`). Place in `contentstack/src/test/java/com/contentstack/sdk/`.
- **Instrumented tests:** Place in `contentstack/src/androidTest/java/com/contentstack/sdk/`. Use **AndroidJUnitRunner**; naming may use `*TestCase` or `Test*` as in the project (e.g. `AssetTestCase`, `EntryTestCase`).

## JUnit 4 usage

- Use **JUnit 4** APIs: `@Test`, `@Before`, `@After`, `@BeforeClass`, `@AfterClass`.
- Use **assertions** from `org.junit.Assert` (e.g. `assertEquals`, `assertNotNull`).
- For unit tests on JVM, **Robolectric** provides Android context; use `Robolectric.buildService(...)` or equivalent where a Context is needed.

## Unit vs instrumented

- **Unit tests** (`src/test/`): Run on JVM with Robolectric; use **MockWebServer** (OkHttp) for HTTP when appropriate; mock dependencies with Mockito/PowerMock as needed.
- **Instrumented tests** (`src/androidTest/`): Run on device/emulator; may use real stack credentials from `BuildConfig` / `local.properties`; avoid flakiness (timeouts, IdlingResource if using Espresso).

## Test data and credentials

- **Credentials:** Instrumented tests may use `BuildConfig` fields (APIKey, deliveryToken, environment, host) populated from `local.properties`. Do not commit real tokens; document required keys in README or test docs.
- **Fixtures:** Use existing test assets and JSON under `src/test/` where applicable.

## Coverage

- **JaCoCo** is configured in `contentstack/build.gradle`. Run `./gradlew :contentstack:jacocoTestReport` for unit-test coverage (e.g. `contentstack/build/reports/jacoco/`). Maintain or improve coverage when adding or changing production code.
25 changes: 21 additions & 4 deletions .github/workflows/sca-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'

- name: Set up Android SDK
uses: android-actions/setup-android@v3
with:
packages: 'tools platform-tools platforms;android-34 build-tools;34.0.0'

- name: Setup local.properties
run: |
cat << EOF >> local.properties
Expand All @@ -18,12 +31,16 @@ jobs:
contentType="${{ secrets.CONTENT_TYPE }}"
assetUid="${{ secrets.ASSET_UID }}"
EOF
- name: Run Snyk to check for vulnerabilities

- name: Set up Snyk
uses: snyk/actions/setup@master
with:
snyk-version: latest

- name: Run Snyk
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --fail-on=all --all-sub-projects
json: true
run: snyk test --fail-on=all --all-sub-projects --json > snyk.json
continue-on-error: true

- uses: contentstack/sca-policy@main
54 changes: 54 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Contentstack Android CDA SDK – Agent Guide

This document is the main entry point for AI agents working in this repository.

## Project

- **Name:** Contentstack Android CDA SDK (contentstack-android)
- **Purpose:** Android client for the Contentstack **Content Delivery API (CDA)**. It fetches content (entries, assets, content types, sync, etc.) from Contentstack for Android apps.
- **Repo:** [contentstack-android](https://github.com/contentstack/contentstack-android)

## Tech stack

- **Languages:** Java (primary SDK source); Kotlin may appear in tests or future code. Target **Java 8** compatibility for the library (see `contentstack/build.gradle`).
- **Build:** Gradle (Android Gradle Plugin), single module **`contentstack`** (AAR).
- **Testing:** JUnit 4, Robolectric (unit tests on JVM), Mockito / PowerMock where used; **androidTest** with AndroidX Test / Espresso for instrumented tests; JaCoCo for coverage (`jacocoTestReport`).
- **HTTP:** **Volley** (`CSHttpConnection`) for much of the CDA traffic; **Retrofit 2 + OkHttp + Gson** for paths such as taxonomy (`Stack`, `APIService`). OkHttp **MockWebServer** in unit tests.

## Main entry points

- **`Contentstack`** – Factory: `Contentstack.stack(Context, apiKey, deliveryToken, environment)` (and overloads with `Config`) returns a **`Stack`**.
- **`Stack`** – Main API: content types, entries, queries, assets, sync, etc.
- **`Config`** – Optional configuration: host, version, region, branch, proxy, connection pool, endpoint.
- **Paths:** `contentstack/src/main/java/com/contentstack/sdk/` (production), `contentstack/src/test/java/com/contentstack/sdk/` (unit tests), `contentstack/src/androidTest/java/` (instrumented tests).

## Commands

Run from the **repository root** (requires Android SDK / `local.properties` for connected tests).

| Goal | Command |
|------|---------|
| **Build library (debug)** | `./gradlew :contentstack:assembleDebug` |
| **Run all unit tests** | `./gradlew :contentstack:testDebugUnitTest` |
| **Run instrumented / connected tests** | `./gradlew :contentstack:connectedDebugAndroidTest` (device or emulator required) |
| **Unit + connected (full local test pass)** | `./gradlew :contentstack:testDebugUnitTest :contentstack:connectedDebugAndroidTest` |
| **Coverage report (unit)** | `./gradlew :contentstack:jacocoTestReport` |

Instrumented tests may need **`local.properties`** entries (e.g. `APIKey`, `deliveryToken`, `environment`, `host`) for stacks that hit a real CDA endpoint—see `contentstack/build.gradle` `buildConfigField` usage.

## Rules and skills

- **`.cursor/rules/`** – Cursor rules for this repo:
- **README.md** – Index of all rules (globs / always-on).
- **dev-workflow.md** – Branches, tests, PR expectations.
- **java.mdc** – Applies to `**/*.java` and `**/*.kt`: language style, `com.contentstack.sdk` layout, logging, null-safety.
- **contentstack-android-cda.mdc** – SDK core: CDA patterns, Stack/Config, host/version/region/branch, retry, callbacks, CDA alignment.
- **testing.mdc** – `contentstack/src/test/**` and `contentstack/src/androidTest/**`: naming, unit vs instrumented, JaCoCo.
- **code-review.mdc** – Always applied: PR/review checklist (aligned with Java CDA SDK).
- **`skills/`** – Reusable skill docs:
- **contentstack-android-cda** – Implementing or changing CDA behavior (Stack/Config, entries, assets, sync, HTTP, callbacks).
- **testing** – Writing or refactoring tests.
- **code-review** – PR review / pre-PR checklist.
- **framework** – Config, HTTP layer (Volley + Retrofit/OkHttp), retry/timeouts.

Refer to `.cursor/rules/README.md` and `skills/README.md` for details.
76 changes: 43 additions & 33 deletions contentstack/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ android {
includeAndroidResources = true
returnDefaultValues = true
all {
testLogging {
events 'passed', 'skipped', 'failed'
}
jacoco {
includeNoLocationClasses = true
excludes = ['jdk.internal.*']
Expand Down Expand Up @@ -126,61 +129,57 @@ dependencies {
def junit = "4.13.2"
def mockito = "5.2.0"
def mockitoKotlin = "2.2.0"
def okhttp = "5.3.2"
def retrofit = "2.11.0"
configurations.configureEach { resolutionStrategy.force 'com.android.support:support-annotations:23.1.0' }
// androidx.test pulls an older kotlin-stdlib; align for Snyk SNYK-JAVA-ORGJETBRAINSKOTLIN-2393744 (fixed in Kotlin 2.1.0+).
configurations.configureEach {
resolutionStrategy.eachDependency { details ->
if (details.requested.group == 'org.jetbrains.kotlin' &&
(details.requested.name == 'kotlin-stdlib' ||
details.requested.name == 'kotlin-stdlib-common' ||
details.requested.name == 'kotlin-stdlib-jdk7' ||
details.requested.name == 'kotlin-stdlib-jdk8')) {
details.useVersion '2.1.21'
}
}
}
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "com.android.volley:volley:$volley"
implementation "junit:junit:$junit"

// For AGP 7.4+
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'

// Unit Testing Dependencies
testImplementation 'junit:junit:4.13.2'
testImplementation "junit:junit:$junit"
testImplementation "org.mockito:mockito-core:$mockito"
testImplementation "org.mockito:mockito-inline:$mockito"
testImplementation 'org.mockito:mockito-android:5.2.0'
testImplementation 'org.robolectric:robolectric:4.15' // Updated to fix security vulnerabilities
testImplementation 'androidx.test:core:1.5.0'
testImplementation 'androidx.test:runner:1.5.2'
testImplementation 'androidx.test.ext:junit:1.1.5'
testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'
testImplementation 'androidx.test:core:1.6.1'
testImplementation 'androidx.test:runner:1.6.1'
testImplementation 'androidx.test.ext:junit:1.2.1'
testImplementation "com.squareup.okhttp3:mockwebserver:$okhttp"
testImplementation 'org.json:json:20231013'
// PowerMock for advanced mocking
testImplementation 'org.powermock:powermock-module-junit4:2.0.9'
testImplementation 'org.powermock:powermock-api-mockito2:2.0.9'
testImplementation 'org.powermock:powermock-core:2.0.9'

// Android Test Dependencies
androidTestImplementation 'androidx.test:core:1.5.0'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation('androidx.test.espresso:espresso-core:3.5.1', {
androidTestImplementation 'androidx.test:core:1.6.1'
androidTestImplementation 'androidx.test:runner:1.6.1'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation('androidx.test.espresso:espresso-core:3.6.1', {
exclude group: 'com.android.support', module: 'support-annotations'
})

// implementation 'com.squareup.okio:okio:3.9.0'
implementation 'com.github.rjeschke:txtmark:0.12'
// // Retrofit
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation 'com.squareup.retrofit2:converter-gson'
// // OkHttp
implementation 'com.squareup.okhttp3:okhttp'
implementation "com.squareup.retrofit2:retrofit:$retrofit"
implementation "com.squareup.retrofit2:converter-gson:$retrofit"
implementation "com.squareup.okhttp3:okhttp:$okhttp"
// implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3'

constraints {
implementation('com.squareup.retrofit2:converter-gson:2.9.0') {
because 'gson 2.8.5 used by retrofit has a vulnerability'
}
implementation('com.google.code.gson:gson@2.8.9') {
because 'gson 2.8.5 used by retrofit has a vulnerability'
}
implementation('com.squareup.okhttp3:okhttp:4.9.3') {
because 'kotlin stdlib 1.4.10 used by okhttp has a vulnerability'
}
implementation('org.jetbrains.kotlin:kotlin-stdlib@1.6.0') {
because 'kotlin stdlib 1.4.10 used by okhttp has a vulnerability'
}
}
}
tasks.register('clearJar', Delete) { delete 'build/libs/contentstack.jar' }
tasks.register('unzip', Copy) {
Expand Down Expand Up @@ -397,7 +396,18 @@ tasks.register('jacocoTestCoverageVerification', JacocoCoverageVerification) {
]))
}

// Make check task depend on coverage verification
tasks.named('check') {
dependsOn('jacocoTestReport', 'jacocoTestCoverageVerification')
// Do not run JVM unit tests during `build`, `check`, etc. Enable only when you ask for them explicitly.
// Examples: ./gradlew :contentstack:testDebugUnitTest | :contentstack:test | :contentstack:jacocoTestReport
def requestedTaskBasenames = gradle.startParameter.taskNames.collect { it.split(':').last().toLowerCase() }
def runJvmUnitTests = requestedTaskBasenames.any { t ->
t == 'test' ||
t ==~ /test.*unittest/ ||
t in ['jacocotestreport', 'jacocotestcoveragewerification', 'jacococombinedreport']
}
gradle.projectsEvaluated {
if (!runJvmUnitTests) {
tasks.matching { it.name.matches('test.*UnitTest') }.configureEach { ut ->
ut.enabled = false
}
}
}
Loading
Loading