Skip to content

Commit ede7243

Browse files
committed
test: add integration test infrastructure with .env support
Set up integration testing framework targeting ArcGIS Online: Infrastructure: - Add dotenvy for loading credentials from .env - Create tests/common/ module with credential loading - Add rate_limit() helper to be polite to AGOL API - All integration tests marked with #[ignore] by default Files: - .env.example - Template for credentials - tests/README.md - Documentation for running integration tests - tests/common/mod.rs - Shared test utilities - tests/integration_basic.rs - Basic connectivity tests Credentials: - Supports ARCGIS_API_KEY (read-only testing) - Supports CLIENT_ID + CLIENT_SECRET (OAuth, future) - .env already in .gitignore Run integration tests with: cargo test --test integration_basic -- --ignored Tests include 500ms rate limiting between API calls to be polite.
1 parent e2840c5 commit ede7243

7 files changed

Lines changed: 251 additions & 0 deletions

File tree

.env.example

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# ArcGIS API Credentials
2+
# Copy this file to .env and fill in your credentials
3+
4+
# Option 1: API Key (simplest for read-only access)
5+
ARCGIS_API_KEY=your_api_key_here
6+
7+
# Option 2: OAuth Client Credentials (for full access)
8+
CLIENT_ID=your_client_id_here
9+
CLIENT_SECRET=your_client_secret_here
10+
11+
# Optional: ArcGIS Online organization URL
12+
# ARCGIS_ORG_URL=https://yourorg.maps.arcgis.com

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ tokio-test = "0.4"
9393
mockito = "1"
9494
tracing-subscriber = "0.3"
9595
anyhow = "1"
96+
dotenvy = "0.15"
9697

9798
[package.metadata.docs.rs]
9899
all-features = true

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,37 @@ Run an example:
124124
cargo run --example query_features
125125
```
126126

127+
## Testing
128+
129+
### Unit Tests
130+
131+
Run unit tests (no credentials required):
132+
133+
```bash
134+
cargo test
135+
```
136+
137+
### Integration Tests
138+
139+
Integration tests require ArcGIS credentials and hit live AGOL APIs.
140+
141+
1. **Set up credentials**:
142+
```bash
143+
cp .env.example .env
144+
# Edit .env and add your ARCGIS_API_KEY or CLIENT_ID/CLIENT_SECRET
145+
```
146+
147+
2. **Run integration tests**:
148+
```bash
149+
# Run all integration tests (be patient, includes rate limiting)
150+
cargo test --test integration_basic -- --ignored
151+
152+
# Run specific test
153+
cargo test --test integration_basic test_public_feature_service_accessible -- --ignored
154+
```
155+
156+
See [`tests/README.md`](tests/README.md) for more details.
157+
127158
## Authentication
128159

129160
### API Key

tests/README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Integration Tests
2+
3+
## Setup
4+
5+
Integration tests require ArcGIS credentials. These tests are marked with `#[ignore]` to avoid hammering the API during normal test runs.
6+
7+
### 1. Create `.env` file
8+
9+
Copy the example file:
10+
11+
```bash
12+
cp .env.example .env
13+
```
14+
15+
### 2. Add your credentials
16+
17+
Edit `.env` and add either:
18+
19+
**Option A: API Key** (recommended for read-only testing):
20+
```bash
21+
ARCGIS_API_KEY=your_api_key_here
22+
```
23+
24+
Get an API key from: https://developers.arcgis.com/dashboard
25+
26+
**Option B: OAuth Credentials** (for full read/write testing):
27+
```bash
28+
CLIENT_ID=your_client_id_here
29+
CLIENT_SECRET=your_client_secret_here
30+
```
31+
32+
### 3. Run integration tests
33+
34+
Run all integration tests (hits live AGOL):
35+
36+
```bash
37+
cargo test --test integration_basic -- --ignored
38+
```
39+
40+
Run specific test:
41+
42+
```bash
43+
cargo test --test integration_basic test_public_feature_service_accessible -- --ignored
44+
```
45+
46+
## Rate Limiting
47+
48+
Tests include `rate_limit()` calls to be polite to the ArcGIS Online API. Do not remove these delays.
49+
50+
## Target Environment
51+
52+
All integration tests target **ArcGIS Online (AGOL)**, not ArcGIS Enterprise.
53+
54+
## Test Organization
55+
56+
- `tests/common/` - Shared utilities, credential loading
57+
- `tests/integration_basic.rs` - Basic connectivity tests
58+
- `tests/integration_feature_service.rs` - Feature Service tests (TODO)
59+
- `tests/integration_geocoding.rs` - Geocoding tests (TODO)
60+
61+
## Public Test Data
62+
63+
Some tests use ESRI's public sample data:
64+
- World Cities Feature Service (read-only, no auth required)
65+
66+
These tests verify basic API structure without requiring credentials.

tests/common/mod.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//! Common utilities for integration tests.
2+
//!
3+
//! These tests target ArcGIS Online (AGOL) and require credentials
4+
//! set in a `.env` file at the repository root.
5+
6+
use arcgis::{ArcGISClient, auth::ApiKeyAuth};
7+
use std::sync::OnceLock;
8+
9+
/// Load environment variables from .env file.
10+
/// Only loads once, subsequent calls are no-ops.
11+
pub fn load_env() {
12+
static INIT: OnceLock<()> = OnceLock::new();
13+
INIT.get_or_init(|| {
14+
dotenvy::dotenv().ok();
15+
});
16+
}
17+
18+
/// Get CLIENT_ID from environment.
19+
pub fn client_id() -> String {
20+
load_env();
21+
std::env::var("CLIENT_ID")
22+
.expect("CLIENT_ID not found in environment. Add to .env file")
23+
}
24+
25+
/// Get CLIENT_SECRET from environment.
26+
pub fn client_secret() -> String {
27+
load_env();
28+
std::env::var("CLIENT_SECRET")
29+
.expect("CLIENT_SECRET not found in environment. Add to .env file")
30+
}
31+
32+
/// Get an optional API key from environment.
33+
/// Some tests may use API key instead of OAuth.
34+
pub fn api_key() -> Option<String> {
35+
load_env();
36+
std::env::var("ARCGIS_API_KEY").ok()
37+
}
38+
39+
/// Create a test client with API key authentication.
40+
///
41+
/// # Panics
42+
///
43+
/// Panics if ARCGIS_API_KEY is not set in environment.
44+
pub fn create_api_key_client() -> ArcGISClient {
45+
let key = api_key().expect("ARCGIS_API_KEY not found in environment. Add to .env file");
46+
let auth = ApiKeyAuth::new(key);
47+
ArcGISClient::new(auth)
48+
}
49+
50+
/// Public ArcGIS Online feature service for testing (read-only).
51+
/// This is ESRI's World Cities sample service.
52+
pub const SAMPLE_FEATURE_SERVICE: &str =
53+
"https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/World_Cities/FeatureServer";
54+
55+
/// Rate limiting helper to be polite to the API.
56+
/// Sleeps for a short duration between requests.
57+
pub async fn rate_limit() {
58+
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
59+
}
60+
61+
#[cfg(test)]
62+
mod tests {
63+
use super::*;
64+
65+
#[test]
66+
fn test_env_loading() {
67+
load_env();
68+
// Just verify it doesn't panic
69+
assert!(std::env::var("CLIENT_ID").is_ok() || std::env::var("ARCGIS_API_KEY").is_ok(),
70+
"Either CLIENT_ID or ARCGIS_API_KEY should be set");
71+
}
72+
}

tests/integration_basic.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//! Basic integration tests for ArcGIS SDK.
2+
//!
3+
//! These tests require credentials in a `.env` file.
4+
//! They are marked with `#[ignore]` by default to avoid hammering the API.
5+
//! Run with: `cargo test --test integration_basic -- --ignored`
6+
7+
mod common;
8+
9+
#[tokio::test]
10+
#[ignore = "Requires API key and hits live API"]
11+
async fn test_client_creation_with_api_key() {
12+
let _client = common::create_api_key_client();
13+
// Client creation should succeed without panicking
14+
// Actual API calls will be tested in more specific tests
15+
}
16+
17+
#[test]
18+
fn test_credentials_available() {
19+
common::load_env();
20+
21+
// Either API key OR OAuth credentials should be available
22+
let has_api_key = common::api_key().is_some();
23+
let has_oauth = std::env::var("CLIENT_ID").is_ok() &&
24+
std::env::var("CLIENT_SECRET").is_ok();
25+
26+
assert!(has_api_key || has_oauth,
27+
"Either ARCGIS_API_KEY or (CLIENT_ID + CLIENT_SECRET) must be set in .env");
28+
}
29+
30+
#[tokio::test]
31+
#[ignore = "Hits live AGOL API - run sparingly"]
32+
async fn test_public_feature_service_accessible() {
33+
// This test verifies we can reach a public AGOL service
34+
// without authentication (read-only)
35+
36+
let client = reqwest::Client::new();
37+
let url = format!("{}?f=json", common::SAMPLE_FEATURE_SERVICE);
38+
39+
common::rate_limit().await;
40+
41+
let response = client
42+
.get(&url)
43+
.send()
44+
.await
45+
.expect("Failed to reach AGOL sample service");
46+
47+
assert!(response.status().is_success(),
48+
"Sample feature service should be accessible");
49+
50+
let json: serde_json::Value = response.json().await
51+
.expect("Response should be valid JSON");
52+
53+
// Verify it's a feature service response
54+
assert!(json.get("layers").is_some() || json.get("tables").is_some(),
55+
"Response should contain layers or tables");
56+
}
57+
58+
// TODO: Add more integration tests as we implement features:
59+
// - test_feature_query_with_auth
60+
// - test_feature_edit_operations
61+
// - test_oauth_flow
62+
// - test_rate_limiting

0 commit comments

Comments
 (0)