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
22 changes: 17 additions & 5 deletions cloudsmith_cli/core/api/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,9 @@ def list_metadata(
response = _request(
client,
"GET",
"metadata",
"packages",
package_slug_perm,
"metadata",
query_params=api_kwargs or None,
)

Expand All @@ -183,9 +183,9 @@ def get_metadata(package_slug_perm: str, metadata_slug_perm: str):
response = _request(
client,
"GET",
"metadata",
"packages",
package_slug_perm,
"metadata",
metadata_slug_perm,
)
return _response_json(response)
Expand All @@ -206,7 +206,7 @@ def create_metadata(
"source_identity": source_identity,
}
response = _request(
client, "POST", "packages", package_slug_perm, "metadata", body=body
client, "POST", "metadata", "packages", package_slug_perm, body=body
)
return _response_json(response)

Expand Down Expand Up @@ -238,9 +238,9 @@ def update_metadata(
response = _request(
client,
"PATCH",
"metadata",
"packages",
package_slug_perm,
"metadata",
metadata_slug_perm,
body=body,
)
Expand All @@ -253,8 +253,20 @@ def delete_metadata(package_slug_perm: str, metadata_slug_perm: str):
_request(
client,
"DELETE",
"metadata",
"packages",
package_slug_perm,
"metadata",
metadata_slug_perm,
)


def validate_metadata(*, content: Any, content_type: str):
"""Validate a metadata payload against its content type schema.

Hits POST /v2/metadata/validate/ which checks shape and schema without
persisting. Server returns 200 on success and 422 on validation failure.
"""
client = get_metadata_api()
body = {"content": content, "content_type": content_type}
_request(client, "POST", "metadata", "validate", body=body)
return True
Comment thread
BartoszBlizniak marked this conversation as resolved.
130 changes: 128 additions & 2 deletions cloudsmith_cli/core/tests/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
API_HOST = "https://api.cloudsmith.io"
PKG = "pkg-slug"
META = "meta-slug"
LIST_URL = f"{API_HOST}/v2/packages/{PKG}/metadata/"
DETAIL_URL = f"{API_HOST}/v2/packages/{PKG}/metadata/{META}/"
LIST_URL = f"{API_HOST}/v2/metadata/packages/{PKG}/"
DETAIL_URL = f"{API_HOST}/v2/metadata/packages/{PKG}/{META}/"
VALIDATE_URL = f"{API_HOST}/v2/metadata/validate/"


@pytest.fixture(autouse=True)
Expand Down Expand Up @@ -440,6 +441,131 @@ def test_422_raises(self):
assert exc_info.value.fields == {"non_field_errors": ["Metadata is read-only."]}


class TestValidateMetadata:
@httpretty.activate(allow_net_connect=False)
def test_success_returns_true(self):
httpretty.register_uri(httpretty.POST, VALIDATE_URL, status=200)

assert (
metadata.validate_metadata(
content={"foo": "bar"}, content_type="application/json"
)
is True
)

sent = _last_request()
assert sent.method == "POST"
assert json.loads(sent.body) == {
"content": {"foo": "bar"},
"content_type": "application/json",
}
assert sent.headers.get("X-Api-Key") == "test-api-key"

@httpretty.activate(allow_net_connect=False)
def test_422_on_non_dict_content(self):
body = {
"code": "invalid",
"detail": "Invalid input.",
"fields": {"content": ["Content must be a JSON object."]},
}
httpretty.register_uri(
httpretty.POST,
VALIDATE_URL,
body=json.dumps(body),
status=422,
content_type="application/json",
)

with pytest.raises(ApiException) as exc_info:
metadata.validate_metadata(
content="not-an-object", content_type="application/json"
)

assert exc_info.value.status == 422
assert exc_info.value.detail == "Invalid input."
assert exc_info.value.fields == {"content": ["Content must be a JSON object."]}

@httpretty.activate(allow_net_connect=False)
def test_422_on_failing_schema(self):
body = {
"code": "invalid",
"detail": "Invalid input.",
"fields": {
"content": [
"Content does not conform to the schema for content type"
" 'application/vnd.jfrog.buildinfo+json'."
]
},
}
httpretty.register_uri(
httpretty.POST,
VALIDATE_URL,
body=json.dumps(body),
status=422,
content_type="application/json",
)

with pytest.raises(ApiException) as exc_info:
metadata.validate_metadata(
content={"bad": "payload"},
content_type="application/vnd.jfrog.buildinfo+json",
)

assert exc_info.value.status == 422
assert exc_info.value.detail == "Invalid input."
assert "content" in exc_info.value.fields

@httpretty.activate(allow_net_connect=False)
def test_422_on_non_customer_writable_content_type(self):
body = {
"code": "invalid",
"detail": "Invalid input.",
"fields": {
"content_type": [
"Content type 'application/vnd.cloudsmith.system+json'"
" is not customer-writable."
]
},
}
httpretty.register_uri(
httpretty.POST,
VALIDATE_URL,
body=json.dumps(body),
status=422,
content_type="application/json",
)

with pytest.raises(ApiException) as exc_info:
metadata.validate_metadata(
content={"foo": "bar"},
content_type="application/vnd.cloudsmith.system+json",
)

assert exc_info.value.status == 422
assert exc_info.value.detail == "Invalid input."
assert "content_type" in exc_info.value.fields

@httpretty.activate(allow_net_connect=False)
def test_401_when_unauthenticated(self):
httpretty.register_uri(
httpretty.POST,
VALIDATE_URL,
body=json.dumps(
{"detail": "Authentication credentials were not provided."}
),
status=401,
content_type="application/json",
)

with pytest.raises(ApiException) as exc_info:
metadata.validate_metadata(
content={"foo": "bar"}, content_type="application/json"
)

assert exc_info.value.status == 401
assert exc_info.value.detail == "Authentication credentials were not provided."


class TestAuthHeaders:
@staticmethod
def _override_config(monkeypatch, *, api_key=None, headers=None):
Expand Down
Loading