Skip to content

Commit fa77daf

Browse files
🔴 test self-referential and circular foreign keys
1 parent 63a5e93 commit fa77daf

File tree

1 file changed

+179
-1
lines changed

1 file changed

+179
-1
lines changed

frictionless/resources/__spec__/table/test_schema.py

Lines changed: 179 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
import sys
2+
from typing import List, Optional
23

34
import pytest
45

5-
from frictionless import Detector, FrictionlessException, Schema, platform
6+
from frictionless import (
7+
Detector,
8+
FrictionlessException,
9+
Package,
10+
Resource,
11+
Schema,
12+
platform,
13+
)
614
from frictionless.resources import TableResource
715

816
BASEURL = "https://raw.githubusercontent.com/frictionlessdata/frictionless-py/master/%s"
@@ -213,3 +221,173 @@ def test_resource_schema_foreign_keys_invalid():
213221
assert rows[2].to_dict() == {"id": 3, "cat": 1, "name": "London"}
214222
assert rows[3].to_dict() == {"id": 4, "cat": 2, "name": "Paris"}
215223
assert rows[4].to_dict() == {"id": 5, "cat": 6, "name": "Rome"}
224+
225+
226+
def _handle_expected_validity_and_errors(
227+
resource: Resource,
228+
expected_validity: List[bool],
229+
expected_errors: List[Optional[str]],
230+
):
231+
rows = resource.read_rows()
232+
print(rows)
233+
for i, (expected_valid, expected_error) in enumerate(
234+
zip(expected_validity, expected_errors)
235+
):
236+
assert rows[i].valid == expected_valid
237+
if expected_error:
238+
assert rows[i].errors[0].type == expected_error
239+
240+
241+
@pytest.mark.parametrize(
242+
"test_case",
243+
[
244+
{
245+
"name": "valid_self_referencing",
246+
"data": [
247+
["eventID", "parentEventID"],
248+
["1", ""],
249+
["2", "1"],
250+
["3", "1"],
251+
["4", "2"],
252+
["5", "3"],
253+
],
254+
"expected_validity": [True, True, True, True, True],
255+
"expected_errors": [None, None, None, None, None],
256+
},
257+
{
258+
"name": "invalid_self_referencing",
259+
"data": [
260+
["eventID", "parentEventID"],
261+
["1", ""],
262+
["2", "1"],
263+
["3", "999"], # Invalid reference to non-existent parent
264+
],
265+
"expected_validity": [True, True, False],
266+
"expected_errors": [None, None, "foreign-key"],
267+
},
268+
],
269+
)
270+
def test_resource_schema_self_referencing_foreign_keys(test_case):
271+
"""Test self-referencing foreign keys with explicit resource reference"""
272+
descriptor = {
273+
"name": "event",
274+
"data": test_case["data"],
275+
"schema": {
276+
"fields": [
277+
{"name": "eventID", "type": "string"},
278+
{"name": "parentEventID", "type": "string"},
279+
],
280+
"primaryKey": ["eventID"],
281+
"foreignKeys": [
282+
{
283+
"fields": "parentEventID",
284+
"reference": {"resource": "event", "fields": "eventID"},
285+
}
286+
],
287+
},
288+
}
289+
290+
resource = TableResource.from_descriptor(descriptor)
291+
292+
_handle_expected_validity_and_errors(
293+
resource, test_case["expected_validity"], test_case["expected_errors"]
294+
)
295+
296+
297+
@pytest.mark.parametrize(
298+
"test_case",
299+
[
300+
{
301+
"name": "valid_circular_references",
302+
"data_a": [
303+
["id", "name", "ref_b"],
304+
[1, "Item A1", 10],
305+
[2, "Item A2", 20],
306+
[3, "Item A3", ""],
307+
],
308+
"data_b": [
309+
["id", "name", "ref_a"],
310+
[10, "Item B1", 1],
311+
[20, "Item B2", 2],
312+
[30, "Item B3", ""],
313+
],
314+
"expected_validity_a": [True, True, True],
315+
"expected_validity_b": [True, True, True],
316+
"expected_errors_a": [None, None, None],
317+
"expected_errors_b": [None, None, None],
318+
},
319+
{
320+
"name": "invalid_circular_references",
321+
"data_a": [
322+
["id", "name", "ref_b"],
323+
[1, "Item A1", 10],
324+
[2, "Item A2", 999], # Invalid reference
325+
],
326+
"data_b": [
327+
["id", "name", "ref_a"],
328+
[10, "Item B1", 1],
329+
[20, "Item B2", 888], # Invalid reference
330+
],
331+
"expected_validity_a": [True, False],
332+
"expected_validity_b": [True, False],
333+
"expected_errors_a": [None, "foreign-key"],
334+
"expected_errors_b": [None, "foreign-key"],
335+
},
336+
],
337+
)
338+
def test_resource_schema_circular_foreign_keys(test_case):
339+
"""Test circular foreign keys between two resources"""
340+
package_descriptor = {
341+
"name": "circular-package",
342+
"resources": [
343+
{
344+
"name": "resource_a",
345+
"data": test_case["data_a"],
346+
"schema": {
347+
"fields": [
348+
{"name": "id", "type": "integer"},
349+
{"name": "name", "type": "string"},
350+
{"name": "ref_b", "type": "integer"},
351+
],
352+
"primaryKey": ["id"],
353+
"foreignKeys": [
354+
{
355+
"fields": "ref_b",
356+
"reference": {"resource": "resource_b", "fields": "id"},
357+
}
358+
],
359+
},
360+
},
361+
{
362+
"name": "resource_b",
363+
"data": test_case["data_b"],
364+
"schema": {
365+
"fields": [
366+
{"name": "id", "type": "integer"},
367+
{"name": "name", "type": "string"},
368+
{"name": "ref_a", "type": "integer"},
369+
],
370+
"primaryKey": ["id"],
371+
"foreignKeys": [
372+
{
373+
"fields": "ref_a",
374+
"reference": {"resource": "resource_a", "fields": "id"},
375+
}
376+
],
377+
},
378+
},
379+
],
380+
}
381+
382+
package = Package.from_descriptor(package_descriptor)
383+
384+
_handle_expected_validity_and_errors(
385+
package.get_resource("resource_a"),
386+
test_case["expected_validity_a"],
387+
test_case["expected_errors_a"],
388+
)
389+
_handle_expected_validity_and_errors(
390+
package.get_resource("resource_b"),
391+
test_case["expected_validity_b"],
392+
test_case["expected_errors_b"],
393+
)

0 commit comments

Comments
 (0)