import copy
import unittest
import compare
import json

class StrictComparisonTests(unittest.TestCase):
    def test_member_order_ignored(self): self.assertTrue(compare.equal({'a': 1, 'b': []}, {'b': [], 'a': 1}))
    def test_int_float(self): self.assertFalse(compare.equal(1, 1.0))
    def test_int_bool(self): self.assertFalse(compare.equal(1, True))
    def test_int_string(self): self.assertFalse(compare.equal(1, '1'))
    def test_empty_array_object(self): self.assertFalse(compare.equal([], {}))
    def test_array_order(self): self.assertFalse(compare.equal([1, 2], [2, 1]))
    def test_missing_field(self): self.assertFalse(compare.equal({'a': 1}, {}))
    def test_extra_field(self): self.assertFalse(compare.equal({}, {'a': 1}))
    def test_null_empty(self): self.assertFalse(compare.equal(None, ''))
    def test_nested_container(self): self.assertFalse(compare.equal({'x': []}, {'x': {}}))
    def test_chinese(self): self.assertFalse(compare.equal('东门', '西门'))
    def test_date_fraction(self): self.assertFalse(compare.equal('2026-10-20 08:30:00', '2026-10-20 08:30:00.000000'))
    def test_wide_integer(self): self.assertFalse(compare.equal(9007199254740993, 9007199254740992))
    def test_pk_string_not_normalized(self):
        c = {'body': {'data': {'detail': {'id': '31'}}}, 'module': 'store', 'independent_original': {'action': [{'id': 31, 'store_id': 2147001571}], 'address': [], 'file': []}}
        with self.assertRaises(KeyError): compare.normalize(c)
    def test_wrong_path_not_normalized(self):
        c = {'body': {'data': {'detail': {'id': 11, 'action_id': 31, 'qrurl_id': 7, 'qrurl': 'http://bad/altered.png'}}}, 'module': 'store', 'independent_original': {'action': [{'id': 31, 'store_id': 2147001571}], 'address': [{'id': 11, 'action_id': 31, 'qrurl_id': 7, 'receive_url_id': 8}], 'file': [{'file_id': 7, 'file_path': 'private/original.png'}]}}
        with self.assertRaises(AssertionError): compare.normalize(c)

class OriginalOracleMutationTests(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        e = json.loads((compare.ROOT / 'green-evidence-B.json').read_text())
        cls.original = next(c for c in e['cases'] if c['scenario'].endswith('TERMINAL_FINAL_DETAIL'))
        cls.meta = json.loads((compare.ROOT / 'structure-B.json').read_text())
    def mutate(self, operation):
        case = copy.deepcopy(self.original); operation(case)
        with self.assertRaises((AssertionError, KeyError, StopIteration)):
            compare.verify(case, self.meta)
    def test_baseline(self): compare.verify(self.original, self.meta)
    def test_id_string(self): self.mutate(lambda c: c['body']['data']['detail'].__setitem__('id', str(c['body']['data']['detail']['id'])))
    def test_id_bool(self): self.mutate(lambda c: c['body']['data']['detail'].__setitem__('id', True))
    def test_title(self): self.mutate(lambda c: c['body']['data']['detail'].__setitem__('title', '被替换的名称'))
    def test_date_fraction(self): self.mutate(lambda c: c['body']['data']['detail'].__setitem__('start_time', '2026-10-20 08:30:00.000000'))
    def test_missing(self): self.mutate(lambda c: c['body']['data']['detail'].pop('status'))
    def test_extra(self): self.mutate(lambda c: c['body']['data']['detail'].__setitem__('extra', None))
    def test_status_string(self): self.mutate(lambda c: c['body'].__setitem__('status', '200'))
    def test_empty_object(self): self.mutate(lambda c: c['body']['data']['detail'].__setitem__('cover_img', {}))
    def test_address_reverse(self): self.mutate(lambda c: c['body']['data']['detail']['addressList'].reverse())
    def test_address_id_string(self): self.mutate(lambda c: c['body']['data']['detail']['addressList'][0].__setitem__('id', str(c['body']['data']['detail']['addressList'][0]['id'])))
    def test_address_text(self): self.mutate(lambda c: c['body']['data']['detail']['addressList'][0].__setitem__('address', '错误地点'))
    def test_qr_url(self): self.mutate(lambda c: c['body']['data']['detail']['addressList'][0].__setitem__('qrurl', 'http://wrong.invalid/png'))
    def test_qr_type(self): self.mutate(lambda c: c['body']['data']['detail']['addressList'][0].__setitem__('qrurl_id', str(c['body']['data']['detail']['addressList'][0]['qrurl_id'])))
    def test_original_field_tamper(self): self.mutate(lambda c: c['independent_original']['action'][-1].__setitem__('title', '被改的原表'))
    def test_false_source_file_path(self): self.mutate(lambda c: c['independent_original']['file'][-4].__setitem__('file_path', 'wrong/path.png'))
    def test_raw_trace_unverified(self): self.mutate(lambda c: c['body'].__setitem__('trace_id', 'anything'))

if __name__ == '__main__': unittest.main()
