Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: new message addition its valid #739

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
351 changes: 351 additions & 0 deletions tests/integration/test_dependencies_compatibility_protobuf.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
Copyright (c) 2019 Aiven Ltd
See LICENSE for details
"""

from karapace.client import Client
from karapace.protobuf.kotlin_wrapper import trim_margin
from karapace.typing import Subject
from tests.utils import create_subject_name_factory

import pytest
Expand Down Expand Up @@ -669,3 +671,352 @@ async def test_protobuf_customer_update_when_having_references(registry_async_cl
res = await registry_async_client.post(f"subjects/{subject_customer}/versions", json=body)

assert res.status_code == 200


async def test_protobuf_schema_compatibility_full_path_renaming(registry_async_client: Client) -> None:
subject_dependency = Subject("dependency")
subject_entity = Subject("entity")

for subject in [subject_dependency, subject_entity]:
res = await registry_async_client.put(f"config/{subject}", json={"compatibility": "FULL"})
assert res.status_code == 200

dependency = """\
syntax = "proto3";
package my.awesome.customer.request.v1beta1;
message RequestId {
string request_id = 1;
}\
"""

original_full_path = """\
syntax = "proto3";
import "my/awesome/customer/request/v1beta1/request_id.proto";
message MessageRequest {
my.awesome.customer.request.v1beta1.RequestId request_id = 1;
}\
"""

evolved_partial_path = """\
syntax = "proto3";
import "my/awesome/customer/request/v1beta1/request_id.proto";
message MessageRequest {
awesome.customer.request.v1beta1.RequestId request_id = 1;
}\
"""

# registering the dependency
body = {"schemaType": "PROTOBUF", "schema": dependency}
res = await registry_async_client.post(f"subjects/{subject_dependency}/versions", json=body)

assert res.status_code == 200

# registering the entity

body = {
"schemaType": "PROTOBUF",
"schema": original_full_path,
"references": [
{
"name": "place.proto",
"subject": subject_dependency,
"version": -1,
}
],
}
res = await registry_async_client.post(f"subjects/{subject_entity}/versions", json=body)
assert res.status_code == 200
previous_id = res.json()["id"]

# registering the evolved entity

body = {
"schemaType": "PROTOBUF",
"schema": evolved_partial_path,
"references": [
{
"name": "place.proto",
"subject": subject_dependency,
"version": -1,
}
],
}
res = await registry_async_client.post(f"subjects/{subject_entity}/versions", json=body)
assert res.status_code == 200
assert res.json()["id"] == previous_id


async def test_protobuf_schema_compatibility_partial_path_renaming(registry_async_client: Client) -> None:
subject_dependency = Subject("dependency")
subject_entity = Subject("entity")

for subject in [subject_dependency, subject_entity]:
res = await registry_async_client.put(f"config/{subject}", json={"compatibility": "FULL"})
assert res.status_code == 200

dependency = """\
syntax = "proto3";
package my.awesome.customer.request.v1beta1;
message RequestId {
string request_id = 1;
}\
"""

original_partial_path = """\
syntax = "proto3";
import "my/awesome/customer/request/v1beta1/request_id.proto";
message MessageRequest {
my.awesome.customer.request.v1beta1.RequestId request_id = 1;
}\
"""

evolved_full_path = """\
syntax = "proto3";
import "my/awesome/customer/request/v1beta1/request_id.proto";
message MessageRequest {
awesome.customer.request.v1beta1.RequestId request_id = 1;
}\
"""

# registering the dependency
body = {"schemaType": "PROTOBUF", "schema": dependency}
res = await registry_async_client.post(f"subjects/{subject_dependency}/versions", json=body)

assert res.status_code == 200

# registering the entity

body = {
"schemaType": "PROTOBUF",
"schema": original_partial_path,
"references": [
{
"name": "place.proto",
"subject": subject_dependency,
"version": -1,
}
],
}
res = await registry_async_client.post(f"subjects/{subject_entity}/versions", json=body)
assert res.status_code == 200
previous_id = res.json()["id"]

# registering the evolved entity

body = {
"schemaType": "PROTOBUF",
"schema": evolved_full_path,
"references": [
{
"name": "place.proto",
"subject": subject_dependency,
"version": -1,
}
],
}
res = await registry_async_client.post(f"subjects/{subject_entity}/versions", json=body)
assert res.status_code == 200
assert (
res.json()["id"] == previous_id
), "The registered schema should be recognized as the same, no evolutions are being applied"


async def test_protobuf_schema_compatibility_import_renaming_should_fail(registry_async_client: Client) -> None:
first_subject_dependency = Subject("first_dependency")
second_subject_dependency = Subject("second_dependency")
subject_entity = Subject("entity")

for subject in [first_subject_dependency, second_subject_dependency, subject_entity]:
res = await registry_async_client.put(f"config/{subject}", json={"compatibility": "FULL"})
assert res.status_code == 200

first_dependency = """\
syntax = "proto3";
package my.awesome.customer.request.v1beta1;
message RequestId {
string request_id = 1;
}\
"""

second_dependency = """\
syntax = "proto3";
package awesome.customer.request.v1beta1;
message RequestId {
string request_id = 1;
}\
"""

original_partial_path = """\
syntax = "proto3";
import "my/awesome/customer/request/v1beta1/request_id.proto";
import "awesome/customer/request/v1beta1/request_id.proto";

message MessageRequest {
awesome.customer.request.v1beta1.RequestId request_id = 1;
}\
"""

evolved_partial_path = """\
syntax = "proto3";
import "awesome/customer/request/v1beta1/request_id.proto";
import "my/awesome/customer/request/v1beta1/request_id.proto";

message MessageRequest {
awesome.customer.request.v1beta1.RequestId request_id = 1;
}\
"""

# registering the first dependency
body = {"schemaType": "PROTOBUF", "schema": first_dependency}
res = await registry_async_client.post(f"subjects/{first_subject_dependency}/versions", json=body)

assert res.status_code == 200

# registering the second dependency
body = {"schemaType": "PROTOBUF", "schema": second_dependency}
res = await registry_async_client.post(f"subjects/{second_subject_dependency}/versions", json=body)

assert res.status_code == 200

# registering the entity
body = {
"schemaType": "PROTOBUF",
"schema": original_partial_path,
"references": [
{
"name": f"{first_subject_dependency}.proto",
"subject": first_subject_dependency,
"version": -1,
},
{
"name": f"{second_subject_dependency}.proto",
"subject": second_subject_dependency,
"version": -1,
},
],
}
res = await registry_async_client.post(f"subjects/{subject_entity}/versions", json=body)
assert res.status_code == 200

# registering the evolved entity
body = {
"schemaType": "PROTOBUF",
"schema": evolved_partial_path,
"references": [
{
"name": f"{first_subject_dependency}.proto",
"subject": first_subject_dependency,
"version": -1,
},
{
"name": f"{second_subject_dependency}.proto",
"subject": second_subject_dependency,
"version": -1,
},
],
}
res = await registry_async_client.post(f"subjects/{subject_entity}/versions", json=body)
assert res.status_code != 200, "The real used type is changed due to the relative import."


@pytest.mark.parametrize("compatibility,expected_to_fail", [("FULL", True), ("FORWARD", True), ("BACKWARD", False)])
async def test_protobuf_schema_update_add_message(
registry_async_client: Client,
compatibility: str,
expected_to_fail: bool,
) -> None:
subject_place = create_subject_name_factory("test_protobuf_place")()
subject_customer = create_subject_name_factory("test_protobuf_customer")()

for subject in [subject_place, subject_customer]:
res = await registry_async_client.put(f"config/{subject}", json={"compatibility": compatibility})
assert res.status_code == 200

place_proto = """\
syntax = "proto3";
package a1;
message Place {
string city = 1;
int32 zone = 2;
}
"""

body = {"schemaType": "PROTOBUF", "schema": place_proto}
res = await registry_async_client.post(f"subjects/{subject_place}/versions", json=body)

assert res.status_code == 200

customer_proto = """\
syntax = "proto3";
package a1;
import "place.proto";
import "google/type/postal_address.proto";
// @producer: another comment
message Customer {
string name = 1;
int32 code = 2;
Place place = 3;
google.type.PostalAddress address = 4;
}
"""
body = {
"schemaType": "PROTOBUF",
"schema": customer_proto,
"references": [
{
"name": "place.proto",
"subject": subject_place,
"version": -1,
}
],
}
res = await registry_async_client.post(f"subjects/{subject_customer}/versions", json=body)

assert res.status_code == 200

customer_proto_updated = """\
syntax = "proto3";
package a1;

import "place.proto";
import "google/type/postal_address.proto";

// @consumer: the comment was incorrect, updating it now
message Customer {
string name = 1;
int32 code = 2;
Place place = 3;
google.type.PostalAddress address = 4;
}
message Bar {
string another = 1;
}
"""

body = {
"schemaType": "PROTOBUF",
"schema": customer_proto_updated,
"references": [
{
"name": "place.proto",
"subject": subject_place,
"version": -1,
}
],
}
res = await registry_async_client.post(f"subjects/{subject_customer}/versions", json=body)

if expected_to_fail:
assert res.status_code == 409
assert res.json() == {
"message": f"Incompatible schema, compatibility_mode={compatibility} Incompatible modification "
f"Modification.MESSAGE_DROP found",
"error_code": 409,
}
else:
assert res.status_code == 200

res = await registry_async_client.get(f"subjects/{subject_customer}/versions/2")

assert res.status_code == 200
assert res.json()["schema"] == customer_proto_updated