|
19 | 19 | import pytest
|
20 | 20 | from google.cloud.bigquery.dataset import (
|
21 | 21 | AccessEntry,
|
| 22 | + Condition, |
22 | 23 | Dataset,
|
23 | 24 | DatasetReference,
|
24 | 25 | Table,
|
@@ -1228,3 +1229,157 @@ def test_table(self):
|
1228 | 1229 | self.assertEqual(table.table_id, "table_id")
|
1229 | 1230 | self.assertEqual(table.dataset_id, dataset_id)
|
1230 | 1231 | self.assertEqual(table.project, project)
|
| 1232 | + |
| 1233 | + |
| 1234 | +class TestCondition: |
| 1235 | + EXPRESSION = 'resource.name.startsWith("projects/my-project/instances/")' |
| 1236 | + TITLE = "Instance Access" |
| 1237 | + DESCRIPTION = "Access to instances in my-project" |
| 1238 | + |
| 1239 | + @pytest.fixture |
| 1240 | + def condition_instance(self): |
| 1241 | + """Provides a Condition instance for tests.""" |
| 1242 | + return Condition( |
| 1243 | + expression=self.EXPRESSION, |
| 1244 | + title=self.TITLE, |
| 1245 | + description=self.DESCRIPTION, |
| 1246 | + ) |
| 1247 | + |
| 1248 | + @pytest.fixture |
| 1249 | + def condition_api_repr(self): |
| 1250 | + """Provides the API representation for the test Condition.""" |
| 1251 | + return { |
| 1252 | + "expression": self.EXPRESSION, |
| 1253 | + "title": self.TITLE, |
| 1254 | + "description": self.DESCRIPTION, |
| 1255 | + } |
| 1256 | + |
| 1257 | + # --- Basic Functionality Tests --- |
| 1258 | + |
| 1259 | + def test_constructor_and_getters_full(self, condition_instance): |
| 1260 | + """Test initialization with all arguments and subsequent attribute access.""" |
| 1261 | + assert condition_instance.expression == self.EXPRESSION |
| 1262 | + assert condition_instance.title == self.TITLE |
| 1263 | + assert condition_instance.description == self.DESCRIPTION |
| 1264 | + |
| 1265 | + def test_constructor_and_getters_minimal(self): |
| 1266 | + """Test initialization with only the required expression.""" |
| 1267 | + condition = Condition(expression=self.EXPRESSION) |
| 1268 | + assert condition.expression == self.EXPRESSION |
| 1269 | + assert condition.title is None |
| 1270 | + assert condition.description is None |
| 1271 | + |
| 1272 | + def test_setters(self, condition_instance): |
| 1273 | + """Test setting attributes after initialization.""" |
| 1274 | + new_title = "New Title" |
| 1275 | + new_desc = "New Description" |
| 1276 | + new_expr = "request.time < timestamp('2024-01-01T00:00:00Z')" |
| 1277 | + |
| 1278 | + condition_instance.title = new_title |
| 1279 | + assert condition_instance.title == new_title |
| 1280 | + |
| 1281 | + condition_instance.description = new_desc |
| 1282 | + assert condition_instance.description == new_desc |
| 1283 | + |
| 1284 | + condition_instance.expression = new_expr |
| 1285 | + assert condition_instance.expression == new_expr |
| 1286 | + |
| 1287 | + # Test setting title and description to empty strings |
| 1288 | + condition_instance.title = "" |
| 1289 | + assert condition_instance.title == "" |
| 1290 | + |
| 1291 | + condition_instance.description = "" |
| 1292 | + assert condition_instance.description == "" |
| 1293 | + |
| 1294 | + # Test setting optional fields back to None |
| 1295 | + condition_instance.title = None |
| 1296 | + assert condition_instance.title is None |
| 1297 | + condition_instance.description = None |
| 1298 | + assert condition_instance.description is None |
| 1299 | + |
| 1300 | + # --- API Representation Tests --- |
| 1301 | + |
| 1302 | + def test_to_api_repr_full(self, condition_instance, condition_api_repr): |
| 1303 | + """Test converting a fully populated Condition to API representation.""" |
| 1304 | + api_repr = condition_instance.to_api_repr() |
| 1305 | + assert api_repr == condition_api_repr |
| 1306 | + |
| 1307 | + def test_to_api_repr_minimal(self): |
| 1308 | + """Test converting a minimally populated Condition to API representation.""" |
| 1309 | + condition = Condition(expression=self.EXPRESSION) |
| 1310 | + expected_api_repr = { |
| 1311 | + "expression": self.EXPRESSION, |
| 1312 | + "title": None, |
| 1313 | + "description": None, |
| 1314 | + } |
| 1315 | + api_repr = condition.to_api_repr() |
| 1316 | + assert api_repr == expected_api_repr |
| 1317 | + |
| 1318 | + def test_from_api_repr_full(self, condition_api_repr): |
| 1319 | + """Test creating a Condition from a full API representation.""" |
| 1320 | + condition = Condition.from_api_repr(condition_api_repr) |
| 1321 | + assert condition.expression == self.EXPRESSION |
| 1322 | + assert condition.title == self.TITLE |
| 1323 | + assert condition.description == self.DESCRIPTION |
| 1324 | + |
| 1325 | + def test_from_api_repr_minimal(self): |
| 1326 | + """Test creating a Condition from a minimal API representation.""" |
| 1327 | + minimal_repr = {"expression": self.EXPRESSION} |
| 1328 | + condition = Condition.from_api_repr(minimal_repr) |
| 1329 | + assert condition.expression == self.EXPRESSION |
| 1330 | + assert condition.title is None |
| 1331 | + assert condition.description is None |
| 1332 | + |
| 1333 | + def test_from_api_repr_with_extra_fields(self): |
| 1334 | + """Test creating a Condition from an API repr with unexpected fields.""" |
| 1335 | + api_repr = { |
| 1336 | + "expression": self.EXPRESSION, |
| 1337 | + "title": self.TITLE, |
| 1338 | + "unexpected_field": "some_value", |
| 1339 | + } |
| 1340 | + condition = Condition.from_api_repr(api_repr) |
| 1341 | + assert condition.expression == self.EXPRESSION |
| 1342 | + assert condition.title == self.TITLE |
| 1343 | + assert condition.description is None |
| 1344 | + # Check that the extra field didn't get added to internal properties |
| 1345 | + assert "unexpected_field" not in condition._properties |
| 1346 | + |
| 1347 | + # # --- Validation Tests --- |
| 1348 | + |
| 1349 | + @pytest.mark.parametrize( |
| 1350 | + "kwargs, error_msg", |
| 1351 | + [ |
| 1352 | + ({"expression": None}, "Pass a non-empty string for expression"), # type: ignore |
| 1353 | + ({"expression": ""}, "expression cannot be an empty string"), |
| 1354 | + ({"expression": 123}, "Pass a non-empty string for expression"), # type: ignore |
| 1355 | + ({"expression": EXPRESSION, "title": 123}, "Pass a string for title, or None"), # type: ignore |
| 1356 | + ({"expression": EXPRESSION, "description": False}, "Pass a string for description, or None"), # type: ignore |
| 1357 | + ], |
| 1358 | + ) |
| 1359 | + def test_validation_init(self, kwargs, error_msg): |
| 1360 | + """Test validation during __init__.""" |
| 1361 | + with pytest.raises(ValueError, match=error_msg): |
| 1362 | + Condition(**kwargs) |
| 1363 | + |
| 1364 | + @pytest.mark.parametrize( |
| 1365 | + "attribute, value, error_msg", |
| 1366 | + [ |
| 1367 | + ("expression", None, "Pass a non-empty string for expression"), # type: ignore |
| 1368 | + ("expression", "", "expression cannot be an empty string"), |
| 1369 | + ("expression", 123, "Pass a non-empty string for expression"), # type: ignore |
| 1370 | + ("title", 123, "Pass a string for title, or None"), # type: ignore |
| 1371 | + ("description", [], "Pass a string for description, or None"), # type: ignore |
| 1372 | + ], |
| 1373 | + ) |
| 1374 | + def test_validation_setters(self, condition_instance, attribute, value, error_msg): |
| 1375 | + """Test validation via setters.""" |
| 1376 | + with pytest.raises(ValueError, match=error_msg): |
| 1377 | + setattr(condition_instance, attribute, value) |
| 1378 | + |
| 1379 | + def test_validation_expression_required_from_api(self): |
| 1380 | + """Test ValueError is raised if expression is missing in from_api_repr.""" |
| 1381 | + api_repr = {"title": self.TITLE} |
| 1382 | + with pytest.raises( |
| 1383 | + ValueError, match="API representation missing required 'expression' field." |
| 1384 | + ): |
| 1385 | + Condition.from_api_repr(api_repr) |
0 commit comments