Skip to content

Commit 6daebc6

Browse files
committed
ci: validate maintained examples
1 parent ae44fad commit 6daebc6

7 files changed

Lines changed: 480 additions & 18 deletions

File tree

.github/dependabot.yml

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: pip
4+
directory: /custom-connector
5+
schedule:
6+
interval: weekly
7+
day: monday
8+
time: "06:00"
9+
timezone: Etc/UTC
10+
groups:
11+
custom-connector-python:
12+
patterns: ["*"]
13+
14+
- package-ecosystem: pip
15+
directory: /custom-graphql
16+
schedule:
17+
interval: weekly
18+
day: monday
19+
time: "06:00"
20+
timezone: Etc/UTC
21+
groups:
22+
custom-graphql-python:
23+
patterns: ["*"]
24+
25+
- package-ecosystem: pip
26+
directory: /dynamic_csv_importer
27+
schedule:
28+
interval: weekly
29+
day: monday
30+
time: "06:00"
31+
timezone: Etc/UTC
32+
groups:
33+
dynamic-csv-python:
34+
patterns: ["*"]
35+
36+
- package-ecosystem: pip
37+
directory: /api-lineage-cicd
38+
schedule:
39+
interval: weekly
40+
day: monday
41+
time: "06:00"
42+
timezone: Etc/UTC
43+
groups:
44+
api-lineage-python:
45+
patterns: ["*"]
46+
47+
- package-ecosystem: pip
48+
directory: /ingestion-automation
49+
schedule:
50+
interval: weekly
51+
day: monday
52+
time: "06:00"
53+
timezone: Etc/UTC
54+
groups:
55+
ingestion-automation-python:
56+
patterns: ["*"]
57+
58+
- package-ecosystem: npm
59+
directory: /custom-graphql/server
60+
schedule:
61+
interval: weekly
62+
day: monday
63+
time: "06:00"
64+
timezone: Etc/UTC
65+
groups:
66+
graphql-server:
67+
patterns: ["*"]
68+
69+
- package-ecosystem: docker
70+
directory: /custom-connector/docker
71+
schedule:
72+
interval: weekly
73+
day: monday
74+
time: "06:00"
75+
timezone: Etc/UTC
76+
77+
- package-ecosystem: github-actions
78+
directory: /
79+
schedule:
80+
interval: weekly
81+
day: monday
82+
time: "06:00"
83+
timezone: Etc/UTC
84+
groups:
85+
github-actions:
86+
patterns: ["*"]
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
name: Validate maintained examples
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
workflow_dispatch:
9+
schedule:
10+
- cron: "0 6 * * 1"
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
python:
17+
name: Python ${{ matrix.python-version }}
18+
runs-on: ubuntu-latest
19+
strategy:
20+
fail-fast: false
21+
matrix:
22+
python-version: ["3.10", "3.11", "3.12"]
23+
steps:
24+
- name: Check out the repository
25+
uses: actions/checkout@v7
26+
27+
- name: Set up Python
28+
uses: actions/setup-python@v7
29+
with:
30+
python-version: ${{ matrix.python-version }}
31+
cache: pip
32+
cache-dependency-path: |
33+
custom-connector/pyproject.toml
34+
custom-graphql/pyproject.toml
35+
dynamic_csv_importer/requirements.txt
36+
api-lineage-cicd/requirements.txt
37+
ingestion-automation/requirements.txt
38+
39+
- name: Install exact OpenMetadata RC and test dependencies
40+
run: |
41+
python -m pip install --upgrade pip
42+
python -m pip install --editable './custom-connector[test]'
43+
python -m pip install --editable ./custom-graphql
44+
python -m pip install \
45+
--requirement dynamic_csv_importer/requirements.txt \
46+
--requirement api-lineage-cicd/requirements.txt \
47+
--requirement ingestion-automation/requirements.txt
48+
49+
- name: Verify the installed OpenMetadata version
50+
run: |
51+
python - <<'PY'
52+
from importlib.metadata import version
53+
54+
assert version("openmetadata-ingestion") == "2.0.0.0rc1"
55+
PY
56+
57+
- name: Compile Python sources
58+
run: >-
59+
python -m compileall -q
60+
custom-connector/src
61+
custom-connector/tests
62+
custom-graphql
63+
dynamic_csv_importer
64+
api-lineage-cicd
65+
ingestion-automation
66+
tests
67+
68+
- name: Lint and check formatting
69+
run: |
70+
ruff check .
71+
ruff format --check .
72+
73+
- name: Run model, configuration, import, and request tests
74+
run: pytest
75+
76+
node:
77+
name: Node fixture
78+
runs-on: ubuntu-latest
79+
defaults:
80+
run:
81+
working-directory: custom-graphql/server
82+
steps:
83+
- name: Check out the repository
84+
uses: actions/checkout@v7
85+
86+
- name: Set up Node.js
87+
uses: actions/setup-node@v7
88+
with:
89+
node-version: 24
90+
cache: npm
91+
cache-dependency-path: custom-graphql/server/package-lock.json
92+
93+
- name: Install locked dependencies
94+
run: npm ci --ignore-scripts
95+
96+
- name: Audit dependencies
97+
run: npm audit --audit-level=high
98+
99+
- name: Check JavaScript syntax
100+
run: |
101+
node --check index.js
102+
node --check resolvers.js
103+
node --check schema.js
104+
105+
compose:
106+
name: Docker Compose configuration
107+
runs-on: ubuntu-latest
108+
steps:
109+
- name: Check out the repository
110+
uses: actions/checkout@v7
111+
112+
- name: Download the official OpenMetadata 2.0 RC1 Compose file
113+
run: >-
114+
curl --fail --silent --show-error --location
115+
--output "${RUNNER_TEMP}/docker-compose-postgres.yml"
116+
https://github.com/open-metadata/OpenMetadata/releases/download/2.0.0-rc1-release/docker-compose-postgres.yml
117+
118+
- name: Validate the Compose overlay
119+
run: >-
120+
docker compose
121+
--project-directory "${GITHUB_WORKSPACE}"
122+
--file "${RUNNER_TEMP}/docker-compose-postgres.yml"
123+
--file custom-connector/docker/compose.override.yml
124+
config --quiet
125+
126+
links:
127+
name: Markdown links
128+
runs-on: ubuntu-latest
129+
steps:
130+
- name: Check out the repository
131+
uses: actions/checkout@v7
132+
133+
- name: Check Markdown links
134+
uses: lycheeverse/lychee-action@v2.9.0
135+
with:
136+
args: --no-progress --config .lychee.toml './**/*.md'
137+
fail: true
138+
139+
secrets:
140+
name: Gitleaks
141+
runs-on: ubuntu-latest
142+
steps:
143+
- name: Check out the repository
144+
uses: actions/checkout@v7
145+
146+
- name: Scan the repository
147+
run: >-
148+
docker run --rm
149+
--volume "${GITHUB_WORKSPACE}:/repo"
150+
zricethezav/gitleaks:v8.27.0
151+
dir /repo --redact --no-banner

.lychee.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
accept = [200, 204, 206, 429]
2+
exclude_all_private = true
3+
max_retries = 3
4+
retry_wait_time = 2
5+
timeout = 20
6+
exclude = [
7+
"^https?://localhost(?::[0-9]+)?(?:/.*)?$",
8+
"^https?://127\\.0\\.0\\.1(?::[0-9]+)?(?:/.*)?$",
9+
"^https://metadata\\.example\\.com/",
10+
"^https://graphql\\.example\\.com/",
11+
"^https://raw\\.githubusercontent\\.com/open-metadata/openmetadata-demo/main/mcp/resources/postgres-script\\.sql$",
12+
]

api-lineage-cicd/tests/test_api_lineage.py

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
configure_from_environment,
1414
create_lineage,
1515
load_yaml,
16+
verify_lineage,
1617
)
1718

1819

@@ -54,21 +55,21 @@ def test_api_yaml_builds_valid_2_0_schema_models() -> None:
5455

5556
def test_api_yaml_builds_valid_2_0_create_requests() -> None:
5657
"""Declarative service, collection, and endpoint inputs must all validate."""
57-
orders = load_yaml("orders_api.yaml")
58-
collection = orders["collections"][0]
59-
endpoint = collection["endpoints"][0]
60-
61-
service_request = build_api_service_request(orders)
62-
collection_request = build_api_collection_request(collection, "orders-api")
63-
endpoint_request = build_api_endpoint_request(
64-
endpoint,
65-
"orders-api.orders",
66-
)
67-
68-
assert service_request.name.root == "orders-api"
69-
assert collection_request.service.root == "orders-api"
70-
assert endpoint_request.apiCollection.root == "orders-api.orders"
71-
assert endpoint_request.requestMethod.value == "POST"
58+
request_counts = {}
59+
for filename in ("orders_api.yaml", "fulfillment_api.yaml"):
60+
raw = load_yaml(filename)
61+
service_request = build_api_service_request(raw)
62+
requests = [service_request]
63+
for collection in raw["collections"]:
64+
collection_fqn = f"{raw['serviceName']}.{collection['name']}"
65+
requests.append(build_api_collection_request(collection, raw["serviceName"]))
66+
requests.extend(
67+
build_api_endpoint_request(endpoint, collection_fqn)
68+
for endpoint in collection["endpoints"]
69+
)
70+
request_counts[filename] = len(requests)
71+
72+
assert request_counts == {"orders_api.yaml": 4, "fulfillment_api.yaml": 4}
7273

7374

7475
def test_lineage_yaml_builds_column_lineage_models() -> None:
@@ -92,9 +93,31 @@ def test_lineage_registration_builds_requests_behind_mocked_sdk_boundaries(
9293
monkeypatch.setattr(Lineage, "add_lineage_request", created.append)
9394
raw = load_yaml("lineage.yaml")
9495

95-
create_lineage({"edges": [raw["edges"][0]]})
96+
create_lineage(raw)
9697

97-
assert len(created) == 1
98+
assert len(created) == 2
9899
assert created[0].edge.fromEntity.type == "apiEndpoint"
99100
assert created[0].edge.toEntity.type == "apiEndpoint"
100101
assert len(created[0].edge.lineageDetails.columnsLineage) == 2
102+
assert len(created[1].edge.lineageDetails.columnsLineage) == 2
103+
104+
105+
def test_lineage_verification_uses_mocked_sdk_boundaries(monkeypatch, capsys) -> None:
106+
"""Verification should request both directions without contacting a server."""
107+
endpoint = SimpleNamespace(id="11111111-1111-1111-1111-111111111111")
108+
lineage = SimpleNamespace(upstreamEdges=[object()], downstreamEdges=[])
109+
calls = []
110+
monkeypatch.setattr(APIEndpoints, "retrieve_by_name", lambda _fqn: endpoint)
111+
112+
def fake_get_entity_lineage(**kwargs):
113+
calls.append(kwargs)
114+
return lineage
115+
116+
monkeypatch.setattr(Lineage, "get_entity_lineage", fake_get_entity_lineage)
117+
118+
verify_lineage()
119+
120+
assert calls[0]["entity_id"] == endpoint.id
121+
assert calls[0]["upstream_depth"] == 2
122+
assert calls[0]["downstream_depth"] == 2
123+
assert "upstream edges: 1, downstream edges: 0" in capsys.readouterr().out

custom-graphql/tests/test_user_updater.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
from pathlib import Path
44

5-
from connector.user_updater import UserUpdaterSource
65
from metadata.config.common import load_config_file
76
from metadata.generated.schema.entity.domains.domain import Domain, DomainType
87
from metadata.generated.schema.entity.teams.user import User
98
from metadata.generated.schema.metadataIngestion.workflow import (
109
OpenMetadataWorkflowConfig,
1110
)
1211

12+
from connector.user_updater import UserUpdaterSource
13+
1314

1415
class FakeMetadata:
1516
"""Small metadata boundary used by the static connector tests."""
@@ -56,6 +57,45 @@ def test_source_uses_the_configured_graphql_endpoint(monkeypatch) -> None:
5657
assert source.graphql_endpoint == "https://graphql.example.com/query"
5758

5859

60+
def test_source_fetches_graphql_records_through_a_mocked_http_boundary(
61+
monkeypatch,
62+
) -> None:
63+
"""GraphQL response handling should be testable without a fixture server."""
64+
calls = []
65+
66+
class FakeResponse:
67+
def raise_for_status(self) -> None:
68+
return None
69+
70+
def json(self) -> dict:
71+
return {
72+
"data": {
73+
"users": [
74+
{
75+
"email": "analyst@example.com",
76+
"displayName": "Data Analyst",
77+
"domain": "Finance",
78+
}
79+
]
80+
}
81+
}
82+
83+
def fake_post(url: str, **kwargs):
84+
calls.append((url, kwargs))
85+
return FakeResponse()
86+
87+
monkeypatch.setattr("connector.user_updater.requests.post", fake_post)
88+
monkeypatch.setenv("GRAPHQL_ENDPOINT", "https://graphql.example.com/query")
89+
workflow = load_config_file(Path("custom-graphql/user_updater.yaml"))
90+
source = UserUpdaterSource.create(workflow["source"], FakeMetadata())
91+
92+
users = source._fetch_users_from_graphql()
93+
94+
assert users[0]["email"] == "analyst@example.com"
95+
assert calls[0][0] == "https://graphql.example.com/query"
96+
assert calls[0][1]["timeout"] == 30
97+
98+
5999
def test_source_builds_an_update_for_a_changed_existing_user(monkeypatch) -> None:
60100
"""GraphQL values should become a valid 2.0 CreateUserRequest."""
61101
monkeypatch.setenv("OPENMETADATA_HOST", "http://localhost:8585/api")

0 commit comments

Comments
 (0)