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
14 changes: 7 additions & 7 deletions spec/tests/dev_e2e/013-jwt-replication.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ the ES config (`reconnect_on_token_change=false`, no file watching).
1. Generate an RSA key pair and sign a JWT token (`sub=user1`, `exp=3600s`).
2. Create the SGW `travel` database (creating its backing CBS bucket and collections) with a `local_jwt` provider registering the public JWK
(`issuer=test-issuer`, `client_id=edge-server`, `algorithms=[RS256]`, `register=true`).
3. Add the JWT user `test-provider_user1` with `["*"]` channel access to `travel.airlines`.
4. Insert 5 documents into CBS (`jwt_test_airline_1..5`) with `channels=["*"]`.
3. Add the JWT user `test-provider_user1` with `["airlines"]` channel access to `travel.airlines`.
4. Insert 5 documents into CBS (`jwt_test_airline_1..5`) with `channels=["airlines"]`.
5. Wait until SGW has imported all 5 documents from CBS.
6. Verify the JWT token authenticates against the SGW REST API (`GET /travel/` with `Authorization: Bearer <token>` returns 200).
7. Configure and start ES with the inline JWT token in `auth.openid_token` and `reconnect_on_token_change=false`.
Expand All @@ -41,7 +41,7 @@ host (`auth.openid_token.path`), across all `travel` collections.
1. Generate an RSA key pair and sign a JWT token (`sub=user1`, `exp=600s`).
2. Create the SGW `travel` database (creating its backing CBS bucket and collections) with a `local_jwt` provider registering the public JWK,
with sync functions for all 5 collections (`airlines`, `airports`, `hotels`, `landmarks`, `routes`).
3. Add the JWT user `test-provider_user1` with `["*"]` channel access to all 5 collections.
3. Add the JWT user `test-provider_user1` with `["United States", "United Kingdom", "France"]` channel access to all 5 collections.
4. Load the `travel` dataset into SGW.
5. Write the JWT token to a file on the ES host (`/home/ec2-user/cert/jwt.txt`).
6. Configure and start ES with `auth.openid_token.path` pointing at the JWT file, syncing all 5 collections.
Expand All @@ -60,7 +60,7 @@ continues uninterrupted.

1. Generate two RSA key pairs (A and B) and sign Token-A (`kid=test-key-1`) and Token-B (`kid=test-key-2`), both `sub=user1`.
2. Create the SGW `travel` database (creating its backing CBS bucket and collections) with a `local_jwt` provider registering both public JWKs (A and B).
3. Add the JWT user `test-provider_user1` with `["*"]` channel access to `travel.airlines`.
3. Add the JWT user `test-provider_user1` with `["airlines"]` channel access to `travel.airlines`.
4. Insert 5 documents into CBS (`rotation_test_1..5`).
5. Write Token-A to the JWT file on the ES host.
6. Configure and start ES with `auth.openid_token.path` and `reconnect_on_token_change=true`, syncing `travel.airlines`.
Expand All @@ -81,7 +81,7 @@ and the replicator permanently stops (no retry, since 401 is not transient).

1. Generate an RSA key pair and sign a valid token; generate a second key pair and sign an invalid token (`kid=unknown-key`) whose key is **not** registered with SGW.
2. Create the SGW `travel` database (creating its backing CBS bucket and collections) with a `local_jwt` provider registering **only** the valid public JWK.
3. Add the JWT user `test-provider_user1` with `["*"]` channel access to `travel.airlines`.
3. Add the JWT user `test-provider_user1` with `["airlines"]` channel access to `travel.airlines`.
4. Insert 3 documents into CBS (`disconnect_test_1..3`).
5. Write the valid token to the JWT file and start ES with `reconnect_on_token_change=true`.
6. Wait until replication is idle (confirming the valid token works).
Expand All @@ -102,7 +102,7 @@ does not treat the content as a valid change.

1. Generate an RSA key pair and sign a valid token.
2. Create the SGW `travel` database (creating its backing CBS bucket and collections) with a `local_jwt` provider registering the public JWK.
3. Add the JWT user `test-provider_user1` with `["*"]` channel access to `travel.airlines`.
3. Add the JWT user `test-provider_user1` with `["airlines"]` channel access to `travel.airlines`.
4. Insert 3 documents into CBS (`filedel_test_1..3`).
5. Write the valid token to the JWT file and start ES with `reconnect_on_token_change=true`.
6. Wait until replication is idle.
Expand All @@ -123,7 +123,7 @@ re-triggered, after which it recovers.

1. Generate key pairs A, C (valid) and one invalid key. Sign Token-A (`kid=test-key-1`), Token-C (`kid=test-key-3`), and an invalid token (`kid=bad-key`), all `sub=user1`.
2. Create the SGW `travel` database (creating its backing CBS bucket and collections) with a `local_jwt` provider registering public JWKs A and C (not the invalid key).
3. Add the JWT user `test-provider_user1` with `["*"]` channel access to `travel.airlines`.
3. Add the JWT user `test-provider_user1` with `["airlines"]` channel access to `travel.airlines`.
4. Insert 3 documents into CBS (`cycle_test_1..3`).
5. **Phase 1:** Write Token-A to the JWT file, start ES with `reconnect_on_token_change=true`, wait until idle, and verify at least 3 documents replicated.
6. **Phase 2:** Overwrite the JWT file with the invalid token. Poll (up to 60s) until the replicator is removed or `Stopped`/`Offline`, confirming a permanent 401 stop.
Expand Down
2 changes: 2 additions & 0 deletions tests/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ Located in `dataset/sg/`. Each has a `-sg.json` (data) and `-sg-config.json` (SG
- **No docstrings or inline step descriptions on test methods** — the matching `spec/tests/**.md` file is the source of truth for the test flow; `mark_test_step()` text mirrors its numbered steps. Helper functions still need docstrings.
- **Platform-agnostic** — must work on every test server (C, .NET, iOS, JVM, JS). Use `self.skip_if_not_platform()` for platform-specific behavior.
- **Version gates** — use `self.skip_if_cbl_not(server, ">= 3.3.0")` / `self.skip_if_sgw_not(sg, ">= 4.0.0")` rather than runtime checks.
- **Never put a document in the `*` channel** — not through a `channels` property, nor a sync function. Sync Gateway already gives every document to `*`, so use a named channel.
- **Prefer named channels for user access** — unless the test checks `*` access, do not grant `*` to a user. Grant the channels that the test documents use.
- **Check the spec first** — every new/modified test must align with `spec/tests/`. Add or update the spec before changing the code.
- **No markdown sidecars for code changes** — do not create `ENHANCEMENT_*.md`, `IMPLEMENTATION_*.md`, etc. PR descriptions cover motivation; the spec covers behavior.

Expand Down
31 changes: 16 additions & 15 deletions tests/dev_e2e/edge_server/test_jwt_rotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,13 @@ async def test_replication_with_jwt_file(self, cblpytest: CBLPyTest, dataset_pat
await cloud.create_database("travel", payload)

# Create user1 with channel access to all travel collections
countries = ["United States", "United Kingdom", "France"]
collection_access_input = {
"travel.airlines": ["*"],
"travel.routes": ["*"],
"travel.airports": ["*"],
"travel.landmarks": ["*"],
"travel.hotels": ["*"],
"travel.airlines": countries,
"travel.routes": countries,
"travel.airports": countries,
"travel.landmarks": countries,
"travel.hotels": countries,
}
access_dict = sgw.create_collection_access_dict(collection_access_input)
await sgw.add_user("travel", "test-provider_user1", "pass", access_dict)
Expand Down Expand Up @@ -182,7 +183,7 @@ async def test_token_rotation_reconnect(self, cblpytest: CBLPyTest, dataset_path
await cloud.create_database("travel", payload)

# Create JWT user with channel access
collection_access_input = {"travel.airlines": ["*"]}
collection_access_input = {"travel.airlines": ["airlines"]}
access_dict = sgw.create_collection_access_dict(collection_access_input)
await sgw.add_user("travel", "test-provider_user1", "pass", access_dict)

Expand All @@ -192,7 +193,7 @@ async def test_token_rotation_reconnect(self, cblpytest: CBLPyTest, dataset_path
doc_id = f"rotation_test_{i}"
doc = {
"id": doc_id,
"channels": ["*"],
"channels": ["airlines"],
"type": "airline",
"name": f"Rotation Airline {i}",
}
Expand Down Expand Up @@ -236,7 +237,7 @@ async def test_token_rotation_reconnect(self, cblpytest: CBLPyTest, dataset_path
doc_id = f"rotation_test_{i}"
doc = {
"id": doc_id,
"channels": ["*"],
"channels": ["airlines"],
"type": "airline",
"name": f"Rotation Airline {i}",
}
Expand Down Expand Up @@ -310,7 +311,7 @@ async def test_invalid_token_rotation_causes_401_stop(
)
await cloud.create_database("travel", payload)

collection_access_input = {"travel.airlines": ["*"]}
collection_access_input = {"travel.airlines": ["airlines"]}
access_dict = sgw.create_collection_access_dict(collection_access_input)
await sgw.add_user("travel", "test-provider_user1", "pass", access_dict)

Expand All @@ -319,7 +320,7 @@ async def test_invalid_token_rotation_causes_401_stop(
doc_id = f"disconnect_test_{i}"
doc = {
"id": doc_id,
"channels": ["*"],
"channels": ["airlines"],
"type": "airline",
"name": f"Disconnect Airline {i}",
}
Expand Down Expand Up @@ -436,7 +437,7 @@ async def test_corrupt_token_file_content_mid_replication(
)
await cloud.create_database("travel", payload)

collection_access_input = {"travel.airlines": ["*"]}
collection_access_input = {"travel.airlines": ["airlines"]}
access_dict = sgw.create_collection_access_dict(collection_access_input)
await sgw.add_user("travel", "test-provider_user1", "pass", access_dict)

Expand All @@ -445,7 +446,7 @@ async def test_corrupt_token_file_content_mid_replication(
doc_id = f"filedel_test_{i}"
doc = {
"id": doc_id,
"channels": ["*"],
"channels": ["airlines"],
"type": "airline",
"name": f"FileDel Airline {i}",
}
Expand Down Expand Up @@ -572,7 +573,7 @@ async def test_valid_invalid_valid_token_cycle(
)
await cloud.create_database("travel", payload)

collection_access_input = {"travel.airlines": ["*"]}
collection_access_input = {"travel.airlines": ["airlines"]}
access_dict = sgw.create_collection_access_dict(collection_access_input)
await sgw.add_user("travel", "test-provider_user1", "pass", access_dict)

Expand All @@ -582,7 +583,7 @@ async def test_valid_invalid_valid_token_cycle(
doc_id = f"cycle_test_{i}"
doc = {
"id": doc_id,
"channels": ["*"],
"channels": ["airlines"],
"type": "airline",
"name": f"Cycle Airline {i}",
}
Expand Down Expand Up @@ -653,7 +654,7 @@ async def test_valid_invalid_valid_token_cycle(
doc_id = f"cycle_test_{i}"
doc = {
"id": doc_id,
"channels": ["*"],
"channels": ["airlines"],
"type": "airline",
"name": f"Cycle Airline {i}",
}
Expand Down
6 changes: 3 additions & 3 deletions tests/dev_e2e/edge_server/test_jwt_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ async def test_jwt_replication_reconnect_false(
# STEP 4: Pre-create JWT user with channel access.
# SGW auto-registers users on first JWT auth (register=true), but the
# auto-created user has no channel access. We pre-create with
# admin_channels=["*"] so it can pull all documents.
# admin_channels=["airlines"] so it can pull the test documents.
# Username format: "<provider>_<sub>" = "test-provider_user1"
# =====================================================================
self.mark_test_step("Adding JWT user with channel access.")
input_data = {"travel.airlines": ["*"]}
input_data = {"travel.airlines": ["airlines"]}
access_dict = sync_gateway.create_collection_access_dict(input_data)
await sync_gateway.add_user(sg_db_name, "test-provider_user1", "pass", access_dict)

Expand All @@ -105,7 +105,7 @@ async def test_jwt_replication_reconnect_false(
doc_id = f"jwt_test_airline_{i}"
doc = {
"id": doc_id,
"channels": ["*"],
"channels": ["airlines"],
"type": "airline",
"name": f"JWT Test Airline {i}",
}
Expand Down
Loading