Skip to content

Commit 0c4f375

Browse files
committed
test(glue): Add unit tests for S3 Tables create_table support
Add three moto-based tests for the S3 Tables code path in GlueCatalog: - create_table succeeds and uses the table warehouse location - create_table rejects an explicit location for S3 Tables databases - create_table raises TableAlreadyExistsError for duplicate tables Moto does not support FederatedDatabase or S3 Tables storage allocation, so the tests patch FakeDatabase.as_dict to return FederatedDatabase and FakeTable.__init__ to inject a StorageDescriptor.Location.
1 parent 9708909 commit 0c4f375

File tree

1 file changed

+137
-0
lines changed

1 file changed

+137
-0
lines changed

tests/catalog/test_glue.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,57 @@
4343
UNIFIED_AWS_SESSION_PROPERTIES,
4444
)
4545

46+
S3TABLES_WAREHOUSE_LOCATION = "s3tables-warehouse-location"
47+
48+
49+
def _patch_moto_for_s3tables(monkeypatch: pytest.MonkeyPatch) -> None:
50+
"""Patch moto to simulate S3 Tables federated databases.
51+
52+
Moto does not support FederatedDatabase on GetDatabase responses or
53+
auto-populating StorageDescriptor.Location for S3 Tables. These patches
54+
simulate the S3 Tables service behavior so that the GlueCatalog S3 Tables
55+
code path can be tested end-to-end with moto.
56+
"""
57+
from moto.glue.models import FakeDatabase, FakeTable
58+
59+
# Patch 1: Make GetDatabase return FederatedDatabase from the stored input.
60+
_original_db_as_dict = FakeDatabase.as_dict
61+
62+
def _db_as_dict_with_federated(self): # type: ignore
63+
result = _original_db_as_dict(self)
64+
if federated := self.input.get("FederatedDatabase"):
65+
result["FederatedDatabase"] = federated
66+
return result
67+
68+
monkeypatch.setattr(FakeDatabase, "as_dict", _db_as_dict_with_federated)
69+
70+
# Patch 2: When a table is created with format=ICEBERG (the S3 Tables convention),
71+
# inject a StorageDescriptor.Location to simulate S3 Tables vending a table
72+
# warehouse location.
73+
_original_table_init = FakeTable.__init__
74+
75+
def _table_init_with_location(self, database_name, table_name, table_input, catalog_id): # type: ignore
76+
if table_input.get("Parameters", {}).get("format") == "ICEBERG" and "StorageDescriptor" not in table_input:
77+
table_input = {
78+
**table_input,
79+
"StorageDescriptor": {
80+
"Columns": [],
81+
"Location": f"s3://{S3TABLES_WAREHOUSE_LOCATION}/{database_name}/{table_name}/",
82+
"InputFormat": "",
83+
"OutputFormat": "",
84+
"SerdeInfo": {},
85+
},
86+
}
87+
_original_table_init(self, database_name, table_name, table_input, catalog_id)
88+
89+
monkeypatch.setattr(FakeTable, "__init__", _table_init_with_location)
90+
91+
# Create a bucket backing the simulated table warehouse location.
92+
# In production, S3 Tables manages this internally; in tests, moto needs
93+
# a real bucket for metadata file writes to succeed.
94+
s3 = boto3.client("s3", region_name="us-east-1")
95+
s3.create_bucket(Bucket=S3TABLES_WAREHOUSE_LOCATION)
96+
4697

4798
@mock_aws
4899
def test_create_table_with_database_location(
@@ -953,3 +1004,89 @@ def test_glue_client_override() -> None:
9531004
test_client = boto3.client("glue", region_name="us-west-2")
9541005
test_catalog = GlueCatalog(catalog_name, test_client)
9551006
assert test_catalog.glue is test_client
1007+
1008+
1009+
@mock_aws
1010+
def test_create_table_s3tables(
1011+
monkeypatch: pytest.MonkeyPatch,
1012+
_bucket_initialize: None,
1013+
moto_endpoint_url: str,
1014+
table_schema_nested: Schema,
1015+
database_name: str,
1016+
table_name: str,
1017+
) -> None:
1018+
_patch_moto_for_s3tables(monkeypatch)
1019+
1020+
identifier = (database_name, table_name)
1021+
test_catalog = GlueCatalog("s3tables", **{"s3.endpoint": moto_endpoint_url})
1022+
test_catalog.glue.create_database(
1023+
DatabaseInput={
1024+
"Name": database_name,
1025+
"FederatedDatabase": {
1026+
"Identifier": "arn:aws:s3tables:us-east-1:123456789012:bucket/my-bucket",
1027+
"ConnectionType": "aws:s3tables",
1028+
},
1029+
}
1030+
)
1031+
1032+
table = test_catalog.create_table(identifier, table_schema_nested)
1033+
assert table.name() == identifier
1034+
assert table.location() == f"s3://{S3TABLES_WAREHOUSE_LOCATION}/{database_name}/{table_name}"
1035+
assert table.metadata_location.startswith(f"s3://{S3TABLES_WAREHOUSE_LOCATION}/{database_name}/{table_name}/metadata/00000-")
1036+
assert table.metadata_location.endswith(".metadata.json")
1037+
assert test_catalog._parse_metadata_version(table.metadata_location) == 0
1038+
1039+
1040+
@mock_aws
1041+
def test_create_table_s3tables_rejects_location(
1042+
monkeypatch: pytest.MonkeyPatch,
1043+
_bucket_initialize: None,
1044+
moto_endpoint_url: str,
1045+
table_schema_nested: Schema,
1046+
database_name: str,
1047+
table_name: str,
1048+
) -> None:
1049+
_patch_moto_for_s3tables(monkeypatch)
1050+
1051+
identifier = (database_name, table_name)
1052+
test_catalog = GlueCatalog("s3tables", **{"s3.endpoint": moto_endpoint_url})
1053+
test_catalog.glue.create_database(
1054+
DatabaseInput={
1055+
"Name": database_name,
1056+
"FederatedDatabase": {
1057+
"Identifier": "arn:aws:s3tables:us-east-1:123456789012:bucket/my-bucket",
1058+
"ConnectionType": "aws:s3tables",
1059+
},
1060+
}
1061+
)
1062+
1063+
with pytest.raises(ValueError, match="Cannot specify a location for S3 Tables table"):
1064+
test_catalog.create_table(identifier, table_schema_nested, location="s3://some-bucket/some-path")
1065+
1066+
1067+
@mock_aws
1068+
def test_create_table_s3tables_duplicate(
1069+
monkeypatch: pytest.MonkeyPatch,
1070+
_bucket_initialize: None,
1071+
moto_endpoint_url: str,
1072+
table_schema_nested: Schema,
1073+
database_name: str,
1074+
table_name: str,
1075+
) -> None:
1076+
_patch_moto_for_s3tables(monkeypatch)
1077+
1078+
identifier = (database_name, table_name)
1079+
test_catalog = GlueCatalog("s3tables", **{"s3.endpoint": moto_endpoint_url})
1080+
test_catalog.glue.create_database(
1081+
DatabaseInput={
1082+
"Name": database_name,
1083+
"FederatedDatabase": {
1084+
"Identifier": "arn:aws:s3tables:us-east-1:123456789012:bucket/my-bucket",
1085+
"ConnectionType": "aws:s3tables",
1086+
},
1087+
}
1088+
)
1089+
1090+
test_catalog.create_table(identifier, table_schema_nested)
1091+
with pytest.raises(TableAlreadyExistsError):
1092+
test_catalog.create_table(identifier, table_schema_nested)

0 commit comments

Comments
 (0)