Skip to content

Commit 039cbbf

Browse files
committed
test, format
1 parent 5d81b46 commit 039cbbf

File tree

6 files changed

+163
-35
lines changed

6 files changed

+163
-35
lines changed

airbyte_cdk/cli/manifest_runner/_common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ def check_manifest_runner_dependencies() -> None:
2525
" poetry install --extras manifest-runner\n",
2626
err=True,
2727
)
28-
sys.exit(1)
28+
sys.exit(1)

airbyte_cdk/cli/manifest_runner/_info.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ def info() -> None:
2424
click.echo(f" Uvicorn version: {uvicorn.__version__}")
2525
else:
2626
click.echo("❌ Manifest runner dependencies not installed")
27-
click.echo(" Install with: pip install airbyte-cdk[manifest-runner]")
27+
click.echo(" Install with: pip install airbyte-cdk[manifest-runner]")

airbyte_cdk/cli/manifest_runner/_start.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,4 @@ def start(host: str, port: int, reload: bool) -> None:
3535
host=host,
3636
port=port,
3737
reload=reload,
38-
)
38+
)

airbyte_cdk/manifest_runner/routers/manifest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ def discover(request: DiscoverRequest) -> DiscoverResponse:
8888
source = safe_build_source(request.manifest.model_dump(), request.config.model_dump())
8989
runner = ManifestRunner(source)
9090
catalog = runner.discover(request.config.model_dump())
91+
if catalog is None:
92+
raise HTTPException(status_code=422, detail="Connector did not return a discovered catalog")
9193
return DiscoverResponse(catalog=catalog)
9294

9395

unit_tests/manifest_runner/routers/test_manifest.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,3 +355,135 @@ def test_full_resolve_multiple_dynamic_stream_templates(
355355
template_b_streams = [s for s in dynamic_streams if "2a" in s["name"]]
356356
assert len(template_a_streams) == 1
357357
assert len(template_b_streams) == 1
358+
359+
def test_check_endpoint_success(self, sample_manifest, sample_config, mock_source):
360+
"""Test successful check endpoint call."""
361+
request_data = {
362+
"manifest": sample_manifest,
363+
"config": sample_config,
364+
}
365+
366+
with (
367+
patch(
368+
"airbyte_cdk.manifest_runner.routers.manifest.safe_build_source"
369+
) as mock_safe_build_source,
370+
patch(
371+
"airbyte_cdk.manifest_runner.routers.manifest.ManifestRunner"
372+
) as mock_runner_class,
373+
):
374+
mock_safe_build_source.return_value = mock_source
375+
376+
mock_runner = Mock()
377+
mock_runner.check_connection.return_value = (True, "Connection successful")
378+
mock_runner_class.return_value = mock_runner
379+
380+
response = client.post("/v1/manifest/check", json=request_data)
381+
382+
assert response.status_code == 200
383+
data = response.json()
384+
assert data["success"] is True
385+
assert data["message"] == "Connection successful"
386+
387+
mock_safe_build_source.assert_called_once_with(sample_manifest, sample_config)
388+
mock_runner_class.assert_called_once_with(mock_source)
389+
mock_runner.check_connection.assert_called_once_with(sample_config)
390+
391+
def test_check_endpoint_failure(self, sample_manifest, sample_config, mock_source):
392+
"""Test check endpoint with connection failure."""
393+
request_data = {
394+
"manifest": sample_manifest,
395+
"config": sample_config,
396+
}
397+
398+
with (
399+
patch(
400+
"airbyte_cdk.manifest_runner.routers.manifest.safe_build_source"
401+
) as mock_safe_build_source,
402+
patch(
403+
"airbyte_cdk.manifest_runner.routers.manifest.ManifestRunner"
404+
) as mock_runner_class,
405+
):
406+
mock_safe_build_source.return_value = mock_source
407+
408+
mock_runner = Mock()
409+
mock_runner.check_connection.return_value = (False, "Invalid API key")
410+
mock_runner_class.return_value = mock_runner
411+
412+
response = client.post("/v1/manifest/check", json=request_data)
413+
414+
assert response.status_code == 200
415+
data = response.json()
416+
assert data["success"] is False
417+
assert data["message"] == "Invalid API key"
418+
419+
def test_discover_endpoint_success(self, sample_manifest, sample_config, mock_source):
420+
"""Test successful discover endpoint call."""
421+
from airbyte_protocol_dataclasses.models import AirbyteCatalog, AirbyteStream
422+
423+
request_data = {
424+
"manifest": sample_manifest,
425+
"config": sample_config,
426+
}
427+
428+
# Create mock catalog
429+
mock_catalog = AirbyteCatalog(
430+
streams=[
431+
AirbyteStream(
432+
name="products",
433+
json_schema={"type": "object", "properties": {"id": {"type": "integer"}}},
434+
supported_sync_modes=["full_refresh"],
435+
)
436+
]
437+
)
438+
439+
with (
440+
patch(
441+
"airbyte_cdk.manifest_runner.routers.manifest.safe_build_source"
442+
) as mock_safe_build_source,
443+
patch(
444+
"airbyte_cdk.manifest_runner.routers.manifest.ManifestRunner"
445+
) as mock_runner_class,
446+
):
447+
mock_safe_build_source.return_value = mock_source
448+
449+
mock_runner = Mock()
450+
mock_runner.discover.return_value = mock_catalog
451+
mock_runner_class.return_value = mock_runner
452+
453+
response = client.post("/v1/manifest/discover", json=request_data)
454+
455+
assert response.status_code == 200
456+
data = response.json()
457+
assert "catalog" in data
458+
assert data["catalog"]["streams"][0]["name"] == "products"
459+
460+
mock_safe_build_source.assert_called_once_with(sample_manifest, sample_config)
461+
mock_runner_class.assert_called_once_with(mock_source)
462+
mock_runner.discover.assert_called_once_with(sample_config)
463+
464+
def test_discover_endpoint_missing_catalog(self, sample_manifest, sample_config, mock_source):
465+
"""Test discover endpoint with no catalog throws 422 error."""
466+
request_data = {
467+
"manifest": sample_manifest,
468+
"config": sample_config,
469+
}
470+
471+
with (
472+
patch(
473+
"airbyte_cdk.manifest_runner.routers.manifest.safe_build_source"
474+
) as mock_safe_build_source,
475+
patch(
476+
"airbyte_cdk.manifest_runner.routers.manifest.ManifestRunner"
477+
) as mock_runner_class,
478+
):
479+
mock_safe_build_source.return_value = mock_source
480+
481+
mock_runner = Mock()
482+
mock_runner.discover.return_value = None # No catalog returned
483+
mock_runner_class.return_value = mock_runner
484+
485+
response = client.post("/v1/manifest/discover", json=request_data)
486+
487+
assert response.status_code == 422
488+
data = response.json()
489+
assert "Connector did not return a discovered catalog" in data["detail"]

unit_tests/manifest_runner/test_auth.py

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def test_missing_credentials_with_secret_raises_401(self):
2525
with patch.dict(os.environ, {"AB_JWT_SIGNATURE_SECRET": "test-secret"}):
2626
with pytest.raises(HTTPException) as exc_info:
2727
verify_jwt_token(None)
28-
28+
2929
assert exc_info.value.status_code == 401
3030
assert exc_info.value.detail == "Bearer token required"
3131
assert exc_info.value.headers == {"WWW-Authenticate": "Bearer"}
@@ -34,13 +34,12 @@ def test_invalid_token_raises_401(self):
3434
"""Test that invalid JWT tokens raise 401."""
3535
with patch.dict(os.environ, {"AB_JWT_SIGNATURE_SECRET": "test-secret"}):
3636
invalid_credentials = HTTPAuthorizationCredentials(
37-
scheme="Bearer",
38-
credentials="invalid.jwt.token"
37+
scheme="Bearer", credentials="invalid.jwt.token"
3938
)
40-
39+
4140
with pytest.raises(HTTPException) as exc_info:
4241
verify_jwt_token(invalid_credentials)
43-
42+
4443
assert exc_info.value.status_code == 401
4544
assert exc_info.value.detail == "Invalid token"
4645
assert exc_info.value.headers == {"WWW-Authenticate": "Bearer"}
@@ -49,13 +48,12 @@ def test_malformed_token_raises_401(self):
4948
"""Test that malformed tokens raise 401."""
5049
with patch.dict(os.environ, {"AB_JWT_SIGNATURE_SECRET": "test-secret"}):
5150
malformed_credentials = HTTPAuthorizationCredentials(
52-
scheme="Bearer",
53-
credentials="not-a-jwt-token"
51+
scheme="Bearer", credentials="not-a-jwt-token"
5452
)
55-
53+
5654
with pytest.raises(HTTPException) as exc_info:
5755
verify_jwt_token(malformed_credentials)
58-
56+
5957
assert exc_info.value.status_code == 401
6058
assert exc_info.value.detail == "Invalid token"
6159

@@ -65,16 +63,15 @@ def test_valid_token_passes(self):
6563
payload = {
6664
"exp": datetime.now(timezone.utc) + timedelta(hours=1),
6765
"iat": datetime.now(timezone.utc),
68-
"sub": "test-user"
66+
"sub": "test-user",
6967
}
7068
valid_token = jwt.encode(payload, secret, algorithm="HS256")
71-
69+
7270
with patch.dict(os.environ, {"AB_JWT_SIGNATURE_SECRET": secret}):
7371
valid_credentials = HTTPAuthorizationCredentials(
74-
scheme="Bearer",
75-
credentials=valid_token
72+
scheme="Bearer", credentials=valid_token
7673
)
77-
74+
7875
# Should not raise any exception
7976
verify_jwt_token(valid_credentials)
8077

@@ -84,43 +81,41 @@ def test_expired_token_raises_401(self):
8481
expired_payload = {
8582
"exp": datetime.now(timezone.utc) - timedelta(hours=1),
8683
"iat": datetime.now(timezone.utc) - timedelta(hours=2),
87-
"sub": "test-user"
84+
"sub": "test-user",
8885
}
8986
expired_token = jwt.encode(expired_payload, secret, algorithm="HS256")
90-
87+
9188
with patch.dict(os.environ, {"AB_JWT_SIGNATURE_SECRET": secret}):
9289
expired_credentials = HTTPAuthorizationCredentials(
93-
scheme="Bearer",
94-
credentials=expired_token
90+
scheme="Bearer", credentials=expired_token
9591
)
96-
92+
9793
with pytest.raises(HTTPException) as exc_info:
9894
verify_jwt_token(expired_credentials)
99-
95+
10096
assert exc_info.value.status_code == 401
10197
assert exc_info.value.detail == "Invalid token"
10298

10399
def test_wrong_secret_raises_401(self):
104100
"""Test that tokens signed with wrong secret raise 401."""
105101
correct_secret = "correct-secret"
106102
wrong_secret = "wrong-secret"
107-
103+
108104
payload = {
109105
"exp": datetime.now(timezone.utc) + timedelta(hours=1),
110106
"iat": datetime.now(timezone.utc),
111-
"sub": "test-user"
107+
"sub": "test-user",
112108
}
113109
token_with_wrong_secret = jwt.encode(payload, wrong_secret, algorithm="HS256")
114-
110+
115111
with patch.dict(os.environ, {"AB_JWT_SIGNATURE_SECRET": correct_secret}):
116112
wrong_credentials = HTTPAuthorizationCredentials(
117-
scheme="Bearer",
118-
credentials=token_with_wrong_secret
113+
scheme="Bearer", credentials=token_with_wrong_secret
119114
)
120-
115+
121116
with pytest.raises(HTTPException) as exc_info:
122117
verify_jwt_token(wrong_credentials)
123-
118+
124119
assert exc_info.value.status_code == 401
125120
assert exc_info.value.detail == "Invalid token"
126121

@@ -136,12 +131,11 @@ def test_token_without_required_claims_passes(self):
136131
secret = "test-secret"
137132
minimal_payload = {"custom": "data"} # No exp, iat, sub etc.
138133
minimal_token = jwt.encode(minimal_payload, secret, algorithm="HS256")
139-
134+
140135
with patch.dict(os.environ, {"AB_JWT_SIGNATURE_SECRET": secret}):
141136
minimal_credentials = HTTPAuthorizationCredentials(
142-
scheme="Bearer",
143-
credentials=minimal_token
137+
scheme="Bearer", credentials=minimal_token
144138
)
145-
139+
146140
# Should not raise any exception - we only verify signature
147-
verify_jwt_token(minimal_credentials)
141+
verify_jwt_token(minimal_credentials)

0 commit comments

Comments
 (0)