@@ -0,0 +1,161 @@
|
||||
import io
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from flask import Flask
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
from blueprints import admin_api
|
||||
|
||||
|
||||
class _FakeExportResponse:
|
||||
status_code = 200
|
||||
content = b'xlsx'
|
||||
headers = {
|
||||
'Content-Disposition': 'attachment; filename=dedupe.xlsx',
|
||||
'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
}
|
||||
|
||||
|
||||
class AdminDedupeTotalDataTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.app = Flask(__name__)
|
||||
|
||||
def _menu_access(self):
|
||||
return patch.object(
|
||||
admin_api,
|
||||
'_ensure_backend_menu_access',
|
||||
return_value=('admin', {'id': 7}, None),
|
||||
)
|
||||
|
||||
def test_list_forwards_group_and_formats_item(self):
|
||||
java_response = {
|
||||
'data': {
|
||||
'items': [{
|
||||
'id': 9,
|
||||
'dataValue': 'B012345678',
|
||||
'groupId': 3,
|
||||
'groupName': 'group-a',
|
||||
'uploaderUserId': 7,
|
||||
'username': 'operator',
|
||||
'createdAt': '2026-08-08T12:30:00',
|
||||
}],
|
||||
'total': 1,
|
||||
'page': 1,
|
||||
'pageSize': 15,
|
||||
},
|
||||
}
|
||||
with self.app.test_request_context(
|
||||
'/api/admin/dedupe-total-data?page=1&page_size=15&group_id=3'):
|
||||
with self._menu_access(), patch.object(
|
||||
admin_api,
|
||||
'_proxy_permission_java',
|
||||
return_value=(java_response, None, 200),
|
||||
) as proxy:
|
||||
response = admin_api.list_dedupe_total_data.__wrapped__()
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.get_json()['items'][0]['group_name'], 'group-a')
|
||||
self.assertEqual(proxy.call_args.kwargs['params'], {
|
||||
'page': 1,
|
||||
'pageSize': 15,
|
||||
'keyword': '',
|
||||
'username': '',
|
||||
'operatorId': 7,
|
||||
'groupId': 3,
|
||||
})
|
||||
|
||||
def test_create_and_update_forward_group(self):
|
||||
java_response = {
|
||||
'message': 'ok',
|
||||
'data': {'id': 9, 'dataValue': 'B012345678', 'groupId': 3, 'groupName': 'group-a'},
|
||||
}
|
||||
with self.app.test_request_context(
|
||||
'/api/admin/dedupe-total-data', method='POST',
|
||||
json={'data_value': ' B012345678 ', 'group_id': 3}):
|
||||
with self._menu_access(), patch.object(
|
||||
admin_api,
|
||||
'_proxy_permission_java',
|
||||
return_value=(java_response, None, 200),
|
||||
) as proxy:
|
||||
response = admin_api.create_dedupe_total_data.__wrapped__()
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(proxy.call_args.kwargs['json_data'], {
|
||||
'dataValue': 'B012345678',
|
||||
'groupId': 3,
|
||||
})
|
||||
|
||||
with self.app.test_request_context(
|
||||
'/api/admin/dedupe-total-data/9', method='PUT',
|
||||
json={'data_value': ' C012345678 ', 'group_id': 8}):
|
||||
with self._menu_access(), patch.object(
|
||||
admin_api,
|
||||
'_proxy_permission_java',
|
||||
return_value=(java_response, None, 200),
|
||||
) as proxy:
|
||||
response = admin_api.update_dedupe_total_data.__wrapped__(9)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(proxy.call_args.kwargs['json_data'], {
|
||||
'dataValue': 'C012345678',
|
||||
'groupId': 8,
|
||||
})
|
||||
|
||||
def test_import_forwards_multipart_group(self):
|
||||
with self.app.test_request_context(
|
||||
'/api/admin/dedupe-total-data/import', method='POST',
|
||||
data={'file': (io.BytesIO(b'xlsx'), 'data.xlsx'), 'group_id': '3'}):
|
||||
with self._menu_access(), patch.object(
|
||||
admin_api,
|
||||
'_proxy_permission_java',
|
||||
return_value=({'message': 'started', 'data': {'importId': 'task-1'}}, None, 200),
|
||||
) as proxy:
|
||||
response = admin_api.import_dedupe_total_data.__wrapped__()
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.get_json()['import_id'], 'task-1')
|
||||
self.assertEqual(proxy.call_args.kwargs['data'], {'groupId': 3})
|
||||
self.assertEqual(set(proxy.call_args.kwargs['files']), {'file'})
|
||||
|
||||
def test_export_forwards_group_filter_and_internal_token(self):
|
||||
class _Session:
|
||||
def get(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
return _FakeExportResponse()
|
||||
|
||||
session = _Session()
|
||||
with self.app.test_request_context(
|
||||
'/api/admin/dedupe-total-data/export?group_id=3&username=operator'):
|
||||
with self._menu_access(), patch.object(
|
||||
admin_api,
|
||||
'_get_backend_java_session',
|
||||
return_value=session,
|
||||
), patch.object(admin_api, '_resolve_internal_token', return_value='token'):
|
||||
response = admin_api.export_dedupe_total_data.__wrapped__()
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(session.kwargs['params'], {
|
||||
'operatorId': 7,
|
||||
'username': 'operator',
|
||||
'groupId': 3,
|
||||
})
|
||||
self.assertEqual(session.kwargs['headers'], {'X-Internal-Token': 'token'})
|
||||
|
||||
def test_import_requires_group(self):
|
||||
with self.app.test_request_context(
|
||||
'/api/admin/dedupe-total-data/import', method='POST',
|
||||
data={'file': (io.BytesIO(b'xlsx'), 'data.xlsx')}):
|
||||
with self._menu_access(), patch.object(admin_api, '_proxy_permission_java') as proxy:
|
||||
response = admin_api.import_dedupe_total_data.__wrapped__()
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertFalse(response.get_json()['success'])
|
||||
proxy.assert_not_called()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,141 @@
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from flask import Flask
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
from blueprints import admin_api
|
||||
|
||||
|
||||
class AdminInvalidAsinDataTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.app = Flask(__name__)
|
||||
|
||||
def test_list_forwards_operator_and_formats_group_fields(self):
|
||||
java_response = {
|
||||
'data': {
|
||||
'items': [{
|
||||
'id': 9,
|
||||
'dataValue': 'B012345678',
|
||||
'brand': 'acme',
|
||||
'groupId': 3,
|
||||
'groupName': 'group-a',
|
||||
'recordSource': 'MANUAL',
|
||||
'createdAt': '2026-08-08T12:30:00',
|
||||
}],
|
||||
'total': 1,
|
||||
'page': 1,
|
||||
'pageSize': 15,
|
||||
},
|
||||
}
|
||||
with self.app.test_request_context('/api/admin/invalid-asin-data?page=1&page_size=15&group_id=3'):
|
||||
with patch.object(
|
||||
admin_api,
|
||||
'_ensure_backend_menu_access',
|
||||
return_value=('admin', {'id': 7}, None),
|
||||
), patch.object(
|
||||
admin_api,
|
||||
'_proxy_permission_java',
|
||||
return_value=(java_response, None, 200),
|
||||
) as proxy:
|
||||
response = admin_api.list_invalid_asin_data.__wrapped__()
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.get_json()['items'], [{
|
||||
'id': 9,
|
||||
'data_value': 'B012345678',
|
||||
'brand': 'acme',
|
||||
'group_id': 3,
|
||||
'group_name': 'group-a',
|
||||
'record_source': 'MANUAL',
|
||||
'created_at': '2026-08-08 12:30',
|
||||
}])
|
||||
self.assertEqual(
|
||||
proxy.call_args.kwargs['params'],
|
||||
{
|
||||
'page': 1,
|
||||
'pageSize': 15,
|
||||
'keyword': '',
|
||||
'groupId': 3,
|
||||
},
|
||||
)
|
||||
self.assertEqual(proxy.call_args.kwargs['current_row'], {'id': 7})
|
||||
|
||||
def test_create_forwards_group_with_trusted_current_user(self):
|
||||
java_response = {
|
||||
'message': 'created',
|
||||
'data': {
|
||||
'id': 9,
|
||||
'dataValue': 'B012345678',
|
||||
'brand': 'acme',
|
||||
'groupId': 3,
|
||||
'groupName': 'group-a',
|
||||
'recordSource': 'MANUAL',
|
||||
},
|
||||
}
|
||||
with self.app.test_request_context(
|
||||
'/api/admin/invalid-asin-data',
|
||||
method='POST',
|
||||
json={'data_value': ' B012345678 ', 'brand': 'Acme', 'group_id': 3},
|
||||
):
|
||||
with patch.object(
|
||||
admin_api,
|
||||
'_ensure_backend_menu_access',
|
||||
return_value=('super_admin', {'id': 1}, None),
|
||||
), patch.object(
|
||||
admin_api,
|
||||
'_proxy_permission_java',
|
||||
return_value=(java_response, None, 200),
|
||||
) as proxy:
|
||||
response = admin_api.create_invalid_asin_data.__wrapped__()
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotIn('params', proxy.call_args.kwargs)
|
||||
self.assertEqual(proxy.call_args.kwargs['current_row'], {'id': 1})
|
||||
self.assertEqual(proxy.call_args.kwargs['json_data'], {
|
||||
'dataValue': 'B012345678',
|
||||
'brand': 'Acme',
|
||||
'groupId': 3,
|
||||
})
|
||||
|
||||
def test_group_list_marks_first_group_as_locked_for_normal_account(self):
|
||||
groups = [
|
||||
{'id': 3, 'groupName': 'group-a'},
|
||||
{'id': 8, 'groupName': 'group-b'},
|
||||
]
|
||||
with self.app.test_request_context('/api/admin/shop-manage-groups'):
|
||||
with patch.object(
|
||||
admin_api,
|
||||
'_ensure_backend_menu_access',
|
||||
return_value=('admin', {'id': 7}, None),
|
||||
), patch.object(
|
||||
admin_api,
|
||||
'_load_expanded_shop_manage_groups',
|
||||
return_value=(groups, None, 200),
|
||||
):
|
||||
response = admin_api.list_shop_manage_groups.__wrapped__()
|
||||
|
||||
body = response.get_json()
|
||||
self.assertEqual(body['locked_group_id'], 3)
|
||||
self.assertEqual([item['id'] for item in body['items']], [3, 8])
|
||||
|
||||
def test_group_list_does_not_lock_super_admin(self):
|
||||
with self.app.test_request_context('/api/admin/shop-manage-groups'):
|
||||
with patch.object(
|
||||
admin_api,
|
||||
'_ensure_backend_menu_access',
|
||||
return_value=('super_admin', {'id': 1}, None),
|
||||
), patch.object(
|
||||
admin_api,
|
||||
'_load_expanded_shop_manage_groups',
|
||||
return_value=([{'id': 3, 'groupName': 'group-a'}], None, 200),
|
||||
):
|
||||
response = admin_api.list_shop_manage_groups.__wrapped__()
|
||||
|
||||
self.assertIsNone(response.get_json()['locked_group_id'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -32,7 +32,7 @@ class _FakeCursor:
|
||||
self.calls.append((sql, tuple(params)))
|
||||
if 'COUNT(*) AS total' in sql:
|
||||
self.kind = 'count'
|
||||
elif 'MAX(t.created_at)' in sql:
|
||||
elif 'AS latest_created_at' in sql and 'GROUP BY' in sql:
|
||||
self.kind = 'groups'
|
||||
self.group_limit = int(params[-2])
|
||||
self.group_offset = int(params[-1])
|
||||
@@ -59,14 +59,14 @@ class _FakeCursor:
|
||||
limited = []
|
||||
for row in rows:
|
||||
shop_key = row['shop_name'].strip().casefold()
|
||||
if counts.get(shop_key, 0) >= 3:
|
||||
if counts.get(shop_key, 0) >= 1:
|
||||
continue
|
||||
counts[shop_key] = counts.get(shop_key, 0) + 1
|
||||
limited.append(row)
|
||||
return limited
|
||||
if self.current_shop is not None:
|
||||
rows = [row for row in rows if row['shop_name'].strip() == self.current_shop]
|
||||
return rows[:3]
|
||||
return rows[:1]
|
||||
return []
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ class AdminShopDataGroupTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.app = Flask(__name__)
|
||||
self.group_rows = [
|
||||
{'shop_name': 'Shop A', 'latest_created_at': datetime(2026, 8, 5, 12, 0)},
|
||||
{'shop_name': 'Shop A', 'latest_created_at': datetime(2026, 8, 7, 5, 21, 45)},
|
||||
{'shop_name': '', 'latest_created_at': datetime(2026, 8, 4, 12, 0)},
|
||||
]
|
||||
self.result_rows = [
|
||||
@@ -98,7 +98,8 @@ class AdminShopDataGroupTest(unittest.TestCase):
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def _result_row(result_id, shop_name, created_at, result_file_url=None):
|
||||
def _result_row(
|
||||
result_id, shop_name, created_at, result_file_url=None, latest_file_updated_at=None):
|
||||
return {
|
||||
'result_id': result_id,
|
||||
'task_id': result_id + 100,
|
||||
@@ -119,6 +120,7 @@ class AdminShopDataGroupTest(unittest.TestCase):
|
||||
'created_at': created_at,
|
||||
'updated_at': created_at,
|
||||
'finished_at': created_at,
|
||||
'latest_file_updated_at': latest_file_updated_at or created_at,
|
||||
'file_job_id': None,
|
||||
'file_status': 'SUCCESS',
|
||||
'username': 'operator',
|
||||
@@ -133,9 +135,25 @@ class AdminShopDataGroupTest(unittest.TestCase):
|
||||
|
||||
self.assertEqual(group['shop_name'], 'Shop A')
|
||||
self.assertEqual(group['group_name'], 'Group 1')
|
||||
self.assertEqual([item['result_id'] for item in group['results']], [5, 4, 3])
|
||||
self.assertEqual([item['result_id'] for item in group['results']], [5])
|
||||
self.assertEqual(group['results'][0]['result_file_url'], 'object-5')
|
||||
|
||||
def test_group_item_falls_back_to_daily_file_update_time(self):
|
||||
row = self._result_row(
|
||||
21427,
|
||||
'Shop A',
|
||||
'2026-08-06T15:20:43',
|
||||
latest_file_updated_at=datetime(2026, 8, 7, 5, 21, 45),
|
||||
)
|
||||
|
||||
group = admin_api._shop_data_crawl_group_item(
|
||||
{'shop_name': 'Shop A', 'latest_created_at': None},
|
||||
{'shop a': [row]},
|
||||
{},
|
||||
)
|
||||
|
||||
self.assertEqual(group['latest_created_at'], '2026-08-07 05:21:45')
|
||||
|
||||
def test_list_paginates_groups_and_ignores_removed_user_status_filters(self):
|
||||
cursor = _FakeCursor(
|
||||
self.group_rows,
|
||||
@@ -160,10 +178,11 @@ class AdminShopDataGroupTest(unittest.TestCase):
|
||||
self.assertEqual(payload['total'], 2)
|
||||
self.assertEqual(payload['page'], 1)
|
||||
self.assertEqual(payload['items'][0]['shop_name'], 'Shop A')
|
||||
self.assertEqual(len(payload['items'][0]['results']), 3)
|
||||
self.assertEqual(payload['items'][0]['latest_created_at'], '2026-08-07 05:21:45')
|
||||
self.assertEqual(len(payload['items'][0]['results']), 1)
|
||||
self.assertEqual(
|
||||
[item['result_id'] for item in payload['items'][0]['results']],
|
||||
[5, 4, 3],
|
||||
[5],
|
||||
)
|
||||
self.assertEqual(payload['items'][1]['shop_name'], '未命名')
|
||||
self.assertEqual(len(payload['items'][1]['results']), 1)
|
||||
@@ -173,7 +192,15 @@ class AdminShopDataGroupTest(unittest.TestCase):
|
||||
self.assertNotIn('FAILED', params)
|
||||
self.assertTrue(any('GROUP BY TRIM(COALESCE(r.source_filename, ' in sql for sql, _ in cursor.calls))
|
||||
self.assertTrue(any('ROW_NUMBER() OVER' in sql for sql, _ in cursor.calls))
|
||||
self.assertTrue(any('shop_row_number <= 3' in sql for sql, _ in cursor.calls))
|
||||
self.assertTrue(any('shop_row_number <= 1' in sql for sql, _ in cursor.calls))
|
||||
self.assertTrue(any(
|
||||
'LEFT JOIN biz_shop_data_crawl_daily_file df ON df.latest_result_id = r.id' in sql
|
||||
for sql, _ in cursor.calls
|
||||
))
|
||||
self.assertTrue(any(
|
||||
'MAX(COALESCE(df.last_success_at, df.updated_at, t.finished_at, t.updated_at, t.created_at))'
|
||||
in sql for sql, _ in cursor.calls
|
||||
))
|
||||
self.assertTrue(any('TRIM(COALESCE(sm.shop_name' in sql for sql, _ in cursor.calls))
|
||||
self.assertTrue(any("TRIM(COALESCE(r.result_file_url, '')) <> ''" in sql for sql, _ in cursor.calls))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user