mirror of
https://github.com/GAM-team/GAM.git
synced 2026-07-05 13:21:35 +00:00
612 lines
24 KiB
Python
612 lines
24 KiB
Python
"""Unit tests for gam.util.csv_pf — RowFilterMatch and CSV output functions.
|
|
|
|
Tests cover all filter types: regex, boolean, count, date, time, length,
|
|
text, data, ranges, and their negations/combinations. Also covers
|
|
CSVPrintFile header processing, title management, and output formatting.
|
|
"""
|
|
|
|
import re
|
|
|
|
import pytest
|
|
import arrow
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# RowFilterMatch tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestRowFilterMatchRegex:
|
|
"""Test regex and notregex filter types."""
|
|
|
|
def test_regex_match(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'name': 'John Smith', 'email': 'john@example.com'}
|
|
titlesList = ['name', 'email']
|
|
# filterVal: (columnPat, anyMatch, filterType, ...)
|
|
columnPat = re.compile('^name$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'regex', re.compile('John', re.IGNORECASE))]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_regex_no_match(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'name': 'Jane Doe'}
|
|
titlesList = ['name']
|
|
columnPat = re.compile('^name$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'regex', re.compile('^John$', re.IGNORECASE))]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is False
|
|
|
|
def test_notregex_match(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'name': 'Jane Doe'}
|
|
titlesList = ['name']
|
|
columnPat = re.compile('^name$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'notregex', re.compile('^John$', re.IGNORECASE))]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_notregex_no_match(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'name': 'John Smith'}
|
|
titlesList = ['name']
|
|
columnPat = re.compile('^name$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'notregex', re.compile('John', re.IGNORECASE))]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is False
|
|
|
|
def test_regex_case_sensitive(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'name': 'john smith'}
|
|
titlesList = ['name']
|
|
columnPat = re.compile('^name$', re.IGNORECASE)
|
|
# Case-sensitive regex should NOT match lowercase
|
|
rowFilter = [(columnPat, True, 'regex', re.compile('^John', 0))]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is False
|
|
|
|
def test_regex_wildcard_column(self):
|
|
"""Regex column pattern matching multiple columns."""
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'name': 'no', 'email': 'yes@match.com'}
|
|
titlesList = ['name', 'email']
|
|
# anyMatch=True: at least one column must match
|
|
columnPat = re.compile('.*', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'regex', re.compile('match', re.IGNORECASE))]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
|
|
class TestRowFilterMatchBoolean:
|
|
"""Test boolean filter type."""
|
|
|
|
def test_boolean_true_match(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'active': 'True'}
|
|
titlesList = ['active']
|
|
columnPat = re.compile('^active$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'boolean', True)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_boolean_true_no_match(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'active': 'False'}
|
|
titlesList = ['active']
|
|
columnPat = re.compile('^active$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'boolean', True)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is False
|
|
|
|
def test_boolean_false_match(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'active': 'False'}
|
|
titlesList = ['active']
|
|
columnPat = re.compile('^active$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'boolean', False)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_boolean_native_bool(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'active': True}
|
|
titlesList = ['active']
|
|
columnPat = re.compile('^active$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'boolean', True)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_boolean_blank_is_false(self):
|
|
"""Blank string should be treated as False."""
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'active': ''}
|
|
titlesList = ['active']
|
|
columnPat = re.compile('^active$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'boolean', False)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
|
|
class TestRowFilterMatchCount:
|
|
"""Test count/number filter types."""
|
|
|
|
def test_count_equals(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'count': '5'}
|
|
titlesList = ['count']
|
|
columnPat = re.compile('^count$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'count', '=', 5)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_count_greater(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'count': '10'}
|
|
titlesList = ['count']
|
|
columnPat = re.compile('^count$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'count', '>', 5)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_count_less(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'count': '3'}
|
|
titlesList = ['count']
|
|
columnPat = re.compile('^count$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'count', '<', 5)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_count_not_equal(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'count': '3'}
|
|
titlesList = ['count']
|
|
columnPat = re.compile('^count$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'count', '!=', 5)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_count_gte(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'count': '5'}
|
|
titlesList = ['count']
|
|
columnPat = re.compile('^count$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'count', '>=', 5)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_count_lte(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'count': '5'}
|
|
titlesList = ['count']
|
|
columnPat = re.compile('^count$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'count', '<=', 5)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_count_blank_is_zero(self):
|
|
"""Blank string count should be treated as 0."""
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'count': ''}
|
|
titlesList = ['count']
|
|
columnPat = re.compile('^count$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'count', '=', 0)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_count_non_digit_no_match(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'count': 'abc'}
|
|
titlesList = ['count']
|
|
columnPat = re.compile('^count$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'count', '=', 0)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is False
|
|
|
|
def test_count_native_int(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'count': 5}
|
|
titlesList = ['count']
|
|
columnPat = re.compile('^count$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'count', '=', 5)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_number_alias(self):
|
|
"""'number' should work the same as 'count'."""
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'val': '42'}
|
|
titlesList = ['val']
|
|
columnPat = re.compile('^val$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'number', '=', 42)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
|
|
class TestRowFilterMatchCountRange:
|
|
"""Test countrange/numberrange filter types."""
|
|
|
|
def test_countrange_in_range(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'count': '5'}
|
|
titlesList = ['count']
|
|
columnPat = re.compile('^count$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'countrange', '=', 1, 10)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_countrange_out_of_range(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'count': '15'}
|
|
titlesList = ['count']
|
|
columnPat = re.compile('^count$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'countrange', '=', 1, 10)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is False
|
|
|
|
def test_countrange_not_equal(self):
|
|
"""countrange with != should return True when value IS outside range."""
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'count': '15'}
|
|
titlesList = ['count']
|
|
columnPat = re.compile('^count$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'countrange', '!=', 1, 10)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_countrange_boundary(self):
|
|
"""Boundary values should be inclusive."""
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'count': '10'}
|
|
titlesList = ['count']
|
|
columnPat = re.compile('^count$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'countrange', '=', 1, 10)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
|
|
class TestRowFilterMatchLength:
|
|
"""Test length and lengthrange filter types."""
|
|
|
|
def test_length_equals(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'name': 'hello'}
|
|
titlesList = ['name']
|
|
columnPat = re.compile('^name$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'length', '=', 5)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_length_greater(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'name': 'hello world'}
|
|
titlesList = ['name']
|
|
columnPat = re.compile('^name$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'length', '>', 5)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_lengthrange_in_range(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'name': 'hello'}
|
|
titlesList = ['name']
|
|
columnPat = re.compile('^name$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'lengthrange', '=', 3, 8)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_length_non_string_no_match(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'name': 12345}
|
|
titlesList = ['name']
|
|
columnPat = re.compile('^name$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'length', '=', 5)]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is False
|
|
|
|
|
|
class TestRowFilterMatchText:
|
|
"""Test text and textrange filter types."""
|
|
|
|
def test_text_equals(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'status': 'active'}
|
|
titlesList = ['status']
|
|
columnPat = re.compile('^status$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'text', '=', 'active')]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_text_not_equal(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'status': 'inactive'}
|
|
titlesList = ['status']
|
|
columnPat = re.compile('^status$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'text', '!=', 'active')]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_text_greater(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'status': 'b'}
|
|
titlesList = ['status']
|
|
columnPat = re.compile('^status$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'text', '>', 'a')]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_textrange_in_range(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'status': 'banana'}
|
|
titlesList = ['status']
|
|
columnPat = re.compile('^status$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'textrange', '=', 'apple', 'cherry')]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_textrange_out_of_range(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'status': 'zebra'}
|
|
titlesList = ['status']
|
|
columnPat = re.compile('^status$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'textrange', '=', 'apple', 'cherry')]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is False
|
|
|
|
|
|
class TestRowFilterMatchData:
|
|
"""Test data and notdata filter types."""
|
|
|
|
def test_data_match(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'role': 'admin'}
|
|
titlesList = ['role']
|
|
columnPat = re.compile('^role$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'data', {'admin', 'owner', 'editor'})]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_data_no_match(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'role': 'viewer'}
|
|
titlesList = ['role']
|
|
columnPat = re.compile('^role$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'data', {'admin', 'owner', 'editor'})]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is False
|
|
|
|
def test_notdata_match(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'role': 'viewer'}
|
|
titlesList = ['role']
|
|
columnPat = re.compile('^role$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'notdata', {'admin', 'owner', 'editor'})]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_notdata_no_match(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'role': 'admin'}
|
|
titlesList = ['role']
|
|
columnPat = re.compile('^role$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'notdata', {'admin', 'owner', 'editor'})]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is False
|
|
|
|
|
|
class TestRowFilterMatchDate:
|
|
"""Test date and time filter types."""
|
|
|
|
def test_date_greater(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
from gamlib import settings as GC
|
|
GC.Values[GC.NEVER_TIME] = 'Never'
|
|
row = {'created': '2025-06-15T10:30:00Z'}
|
|
titlesList = ['created']
|
|
columnPat = re.compile('^created$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'date', '>', '2025-01-01T00:00:00.000Z')]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_date_less(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
from gamlib import settings as GC
|
|
GC.Values[GC.NEVER_TIME] = 'Never'
|
|
row = {'created': '2024-06-15T10:30:00Z'}
|
|
titlesList = ['created']
|
|
columnPat = re.compile('^created$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'date', '<', '2025-01-01T00:00:00.000Z')]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_time_greater(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
from gamlib import settings as GC
|
|
GC.Values[GC.NEVER_TIME] = 'Never'
|
|
row = {'modified': '2025-06-15T10:30:00.000Z'}
|
|
titlesList = ['modified']
|
|
columnPat = re.compile('^modified$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'time', '>', '2025-01-01T00:00:00.000Z')]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_date_empty_no_match(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
from gamlib import settings as GC
|
|
GC.Values[GC.NEVER_TIME] = 'Never'
|
|
row = {'created': ''}
|
|
titlesList = ['created']
|
|
columnPat = re.compile('^created$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'date', '>', '2025-01-01T00:00:00.000Z')]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is False
|
|
|
|
def test_date_non_string_no_match(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
from gamlib import settings as GC
|
|
GC.Values[GC.NEVER_TIME] = 'Never'
|
|
row = {'created': None}
|
|
titlesList = ['created']
|
|
columnPat = re.compile('^created$', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'date', '>', '2025-01-01T00:00:00.000Z')]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is False
|
|
|
|
|
|
class TestRowFilterMatchModes:
|
|
"""Test rowFilter mode combinations (Any vs All) and drop filters."""
|
|
|
|
def test_no_filters_returns_true(self):
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'name': 'anything'}
|
|
assert RowFilterMatch(row, ['name'], [], True, [], True) is True
|
|
|
|
def test_row_filter_mode_any(self):
|
|
"""Any mode: at least one filter must match to select."""
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'a': 'yes', 'b': 'no'}
|
|
titlesList = ['a', 'b']
|
|
f1 = (re.compile('^a$', re.IGNORECASE), True, 'regex', re.compile('yes', re.IGNORECASE))
|
|
f2 = (re.compile('^b$', re.IGNORECASE), True, 'regex', re.compile('yes', re.IGNORECASE))
|
|
# f1 matches, f2 doesn't — Any mode should select
|
|
assert RowFilterMatch(row, titlesList, [f1, f2], False, [], True) is True
|
|
|
|
def test_row_filter_mode_all(self):
|
|
"""All mode: all filters must match to select."""
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'a': 'yes', 'b': 'no'}
|
|
titlesList = ['a', 'b']
|
|
f1 = (re.compile('^a$', re.IGNORECASE), True, 'regex', re.compile('yes', re.IGNORECASE))
|
|
f2 = (re.compile('^b$', re.IGNORECASE), True, 'regex', re.compile('yes', re.IGNORECASE))
|
|
# f1 matches, f2 doesn't — All mode should NOT select
|
|
assert RowFilterMatch(row, titlesList, [f1, f2], True, [], True) is False
|
|
|
|
def test_drop_filter_any(self):
|
|
"""Drop filter in Any mode: any match drops the row."""
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'status': 'deleted'}
|
|
titlesList = ['status']
|
|
dropFilter = [(re.compile('^status$', re.IGNORECASE), True, 'regex', re.compile('deleted', re.IGNORECASE))]
|
|
assert RowFilterMatch(row, titlesList, [], True, dropFilter, False) is False
|
|
|
|
def test_drop_filter_no_match_keeps(self):
|
|
"""Drop filter that doesn't match should keep the row."""
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'status': 'active'}
|
|
titlesList = ['status']
|
|
dropFilter = [(re.compile('^status$', re.IGNORECASE), True, 'regex', re.compile('deleted', re.IGNORECASE))]
|
|
assert RowFilterMatch(row, titlesList, [], True, dropFilter, False) is True
|
|
|
|
def test_row_and_drop_filter_combined(self):
|
|
"""Row selected by rowFilter but dropped by dropFilter."""
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'name': 'John', 'status': 'deleted'}
|
|
titlesList = ['name', 'status']
|
|
rowFilter = [(re.compile('^name$', re.IGNORECASE), True, 'regex', re.compile('John', re.IGNORECASE))]
|
|
dropFilter = [(re.compile('^status$', re.IGNORECASE), True, 'regex', re.compile('deleted', re.IGNORECASE))]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, dropFilter, False) is False
|
|
|
|
def test_column_not_in_titles(self):
|
|
"""Filter column not in titles should use [None] as columns."""
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'name': 'John'}
|
|
titlesList = ['name']
|
|
# Filter on 'missing_col' which isn't in titlesList
|
|
rowFilter = [(re.compile('^missing_col$', re.IGNORECASE), True, 'regex', re.compile('John', re.IGNORECASE))]
|
|
# No columns match, so row.get(None, '') = '', regex doesn't match
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is False
|
|
|
|
def test_any_match_across_columns(self):
|
|
"""anyMatch=True across multiple columns: any column matching suffices."""
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'col1': 'no', 'col2': 'yes'}
|
|
titlesList = ['col1', 'col2']
|
|
# Match any column starting with 'col'
|
|
columnPat = re.compile('^col', re.IGNORECASE)
|
|
rowFilter = [(columnPat, True, 'regex', re.compile('yes', re.IGNORECASE))]
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is True
|
|
|
|
def test_all_match_across_columns(self):
|
|
"""anyMatch=False (all mode) across columns: ALL columns must match."""
|
|
from gam.util.csv_pf import RowFilterMatch
|
|
row = {'col1': 'yes', 'col2': 'no'}
|
|
titlesList = ['col1', 'col2']
|
|
columnPat = re.compile('^col', re.IGNORECASE)
|
|
rowFilter = [(columnPat, False, 'regex', re.compile('yes', re.IGNORECASE))]
|
|
# col2 doesn't match — all mode fails
|
|
assert RowFilterMatch(row, titlesList, rowFilter, True, [], True) is False
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CSVPrintFile title management tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestCSVPrintFileTitles:
|
|
"""Test CSVPrintFile title management methods."""
|
|
|
|
def test_add_title(self):
|
|
from gam.util.csv_pf import CSVPrintFile
|
|
pf = CSVPrintFile()
|
|
pf.AddTitle('email')
|
|
assert 'email' in pf.titlesList
|
|
assert 'email' in pf.titlesSet
|
|
|
|
def test_add_titles_no_duplicate(self):
|
|
from gam.util.csv_pf import CSVPrintFile
|
|
pf = CSVPrintFile()
|
|
pf.AddTitles(['email'])
|
|
pf.AddTitles(['email'])
|
|
assert pf.titlesList.count('email') == 1
|
|
|
|
def test_add_titles(self):
|
|
from gam.util.csv_pf import CSVPrintFile
|
|
pf = CSVPrintFile()
|
|
pf.AddTitles(['email', 'name', 'role'])
|
|
assert pf.titlesList == ['email', 'name', 'role']
|
|
|
|
def test_remove_titles(self):
|
|
from gam.util.csv_pf import CSVPrintFile
|
|
pf = CSVPrintFile()
|
|
pf.AddTitles(['email', 'name', 'role'])
|
|
pf.RemoveTitles(['name'])
|
|
assert 'name' not in pf.titlesList
|
|
assert 'name' not in pf.titlesSet
|
|
|
|
def test_set_titles(self):
|
|
from gam.util.csv_pf import CSVPrintFile
|
|
pf = CSVPrintFile()
|
|
pf.AddTitles(['a', 'b', 'c'])
|
|
pf.SetTitles(['x', 'y'])
|
|
assert pf.titlesList == ['x', 'y']
|
|
assert pf.titlesSet == {'x', 'y'}
|
|
|
|
def test_insert_titles(self):
|
|
from gam.util.csv_pf import CSVPrintFile
|
|
pf = CSVPrintFile()
|
|
pf.AddTitles(['b', 'c'])
|
|
pf.InsertTitles(0, ['a'])
|
|
assert pf.titlesList[0] == 'a'
|
|
|
|
def test_move_titles_to_end(self):
|
|
from gam.util.csv_pf import CSVPrintFile
|
|
pf = CSVPrintFile()
|
|
pf.AddTitles(['a', 'b', 'c'])
|
|
pf.MoveTitlesToEnd(['a'])
|
|
assert pf.titlesList == ['b', 'c', 'a']
|
|
|
|
def test_add_sort_title(self):
|
|
from gam.util.csv_pf import CSVPrintFile
|
|
pf = CSVPrintFile()
|
|
pf.AddSortTitle('email')
|
|
assert 'email' in pf.sortTitlesList
|
|
|
|
def test_add_sort_titles(self):
|
|
from gam.util.csv_pf import CSVPrintFile
|
|
pf = CSVPrintFile()
|
|
pf.AddSortTitles(['email', 'name'])
|
|
assert pf.sortTitlesList == ['email', 'name']
|
|
|
|
|
|
class TestCSVPrintFileRows:
|
|
"""Test CSVPrintFile row management."""
|
|
|
|
def test_write_row_no_filter(self):
|
|
from gam.util.csv_pf import CSVPrintFile
|
|
pf = CSVPrintFile()
|
|
pf.AddTitles(['name', 'email'])
|
|
pf.WriteRowNoFilter({'name': 'John', 'email': 'john@example.com'})
|
|
assert len(pf.rows) == 1
|
|
assert pf.rows[0]['name'] == 'John'
|
|
|
|
def test_append_row(self):
|
|
from gam.util.csv_pf import CSVPrintFile
|
|
pf = CSVPrintFile()
|
|
pf.AddTitles(['name'])
|
|
pf.AppendRow({'name': 'test'})
|
|
assert len(pf.rows) == 1
|
|
|
|
def test_header_filter_match_no_filter(self):
|
|
from gam.util.csv_pf import CSVPrintFile
|
|
pf = CSVPrintFile()
|
|
# No filter = match all
|
|
assert pf.headerFilter == []
|
|
assert pf.headerDropFilter == []
|
|
|
|
def test_header_filter_set(self):
|
|
from gam.util.csv_pf import CSVPrintFile
|
|
pf = CSVPrintFile()
|
|
pattern = [(re.compile('^email$', re.IGNORECASE), True)]
|
|
pf.SetHeaderFilter(pattern)
|
|
assert pf.headerFilter == pattern
|
|
|
|
|
|
class TestCSVPrintFileMapTitles:
|
|
"""Test MapTitles method."""
|
|
|
|
def test_map_titles_basic(self):
|
|
from gam.util.csv_pf import CSVPrintFile
|
|
pf = CSVPrintFile()
|
|
pf.AddTitles(['old_name', 'old_email'])
|
|
pf.MapTitles('old_name', 'name')
|
|
pf.MapTitles('old_email', 'email')
|
|
assert pf.titlesList == ['name', 'email']
|
|
assert 'name' in pf.titlesSet
|
|
assert 'old_name' not in pf.titlesSet
|