2.0.0-rc1 : Profile and achievements update.
This commit is contained in:
+268
-13
@@ -44,9 +44,12 @@ class ApplicationIntegrationTests(unittest.TestCase):
|
||||
paths = [
|
||||
'/',
|
||||
'/matches/',
|
||||
'/reports/',
|
||||
'/awards/',
|
||||
'/players/',
|
||||
f'/players/{self.roster_ids[0]}',
|
||||
'/teams/',
|
||||
'/teams/performance',
|
||||
'/tactics/',
|
||||
'/opponents/',
|
||||
]
|
||||
@@ -56,6 +59,47 @@ class ApplicationIntegrationTests(unittest.TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertGreater(len(response.data), 100)
|
||||
|
||||
def test_match_detail_contains_post_match_report(self):
|
||||
l3 = sqlite3.connect(Config.DB_L3_PATH)
|
||||
try:
|
||||
match_id = l3.execute(
|
||||
"""
|
||||
SELECT match_id
|
||||
FROM dm_match_reports
|
||||
ORDER BY match_date DESC
|
||||
LIMIT 1
|
||||
"""
|
||||
).fetchone()[0]
|
||||
finally:
|
||||
l3.close()
|
||||
response = self.client.get(f'/matches/{match_id}')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('赛后报告'.encode('utf-8'), response.data)
|
||||
self.assertIn('Form Improver'.encode('utf-8'), response.data)
|
||||
|
||||
def test_awards_and_professional_identity_render(self):
|
||||
response = self.client.get('/awards/?type=monthly')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('周期最佳与荣誉榜'.encode('utf-8'), response.data)
|
||||
self.assertIn('月度最佳'.encode('utf-8'), response.data)
|
||||
|
||||
response = self.client.get(f'/players/{self.roster_ids[0]}')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(b'Professional Identity', response.data)
|
||||
self.assertIn('代表武器'.encode('utf-8'), response.data)
|
||||
|
||||
def test_team_performance_page_contains_beta_sections(self):
|
||||
response = self.client.get('/teams/performance')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
for label in (
|
||||
'战队履历与阵容表现',
|
||||
'二人组表现',
|
||||
'实际阵容组合',
|
||||
'Roster Version 历史',
|
||||
):
|
||||
with self.subTest(label=label):
|
||||
self.assertIn(label.encode('utf-8'), response.data)
|
||||
|
||||
def test_admin_integrity_page_and_json_render(self):
|
||||
with self.client.session_transaction() as session:
|
||||
session['is_admin'] = True
|
||||
@@ -93,19 +137,26 @@ class ApplicationIntegrationTests(unittest.TestCase):
|
||||
"""
|
||||
).fetchone()[0]
|
||||
|
||||
response = self.client.post(
|
||||
'/admin/import-match',
|
||||
data={
|
||||
'capture': (
|
||||
io.BytesIO(raw.encode('utf-8')),
|
||||
'iframe_network.json',
|
||||
),
|
||||
},
|
||||
content_type='multipart/form-data',
|
||||
follow_redirects=True,
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(b'already imported with identical data', response.data)
|
||||
upload = io.BytesIO(raw.encode('utf-8'))
|
||||
try:
|
||||
response = self.client.post(
|
||||
'/admin/import-match',
|
||||
data={
|
||||
'capture': (
|
||||
upload,
|
||||
'iframe_network.json',
|
||||
),
|
||||
},
|
||||
content_type='multipart/form-data',
|
||||
follow_redirects=True,
|
||||
)
|
||||
finally:
|
||||
upload.close()
|
||||
try:
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(b'already imported with identical data', response.data)
|
||||
finally:
|
||||
response.close()
|
||||
|
||||
def test_player_search_works_across_l2_and_l3(self):
|
||||
response = self.client.get('/players/?search=jAck')
|
||||
@@ -238,11 +289,18 @@ class L3MartBuilderTests(unittest.TestCase):
|
||||
)
|
||||
l3.commit()
|
||||
for table in (
|
||||
'dm_duo_stats',
|
||||
'dm_lineup_stats',
|
||||
'dm_match_player_reports',
|
||||
'dm_match_reports',
|
||||
'dm_player_awards',
|
||||
'dm_player_match_history',
|
||||
'dm_player_map_stats',
|
||||
'dm_player_period_stats',
|
||||
'dm_player_record_events',
|
||||
'dm_player_records',
|
||||
'dm_player_weapon_stats',
|
||||
'dm_team_season_stats',
|
||||
):
|
||||
count = l3.execute(
|
||||
f'SELECT COUNT(*) FROM {table}'
|
||||
@@ -615,6 +673,203 @@ class DatabaseGovernanceTests(unittest.TestCase):
|
||||
l3.close()
|
||||
l2.close()
|
||||
|
||||
def test_award_minimum_samples_and_unique_period_winner(self):
|
||||
minimums = {
|
||||
'daily': 1,
|
||||
'weekly': 2,
|
||||
'monthly': 5,
|
||||
'quarterly': 10,
|
||||
'yearly': 20,
|
||||
}
|
||||
l3 = sqlite3.connect(Config.DB_L3_PATH)
|
||||
try:
|
||||
rows = l3.execute(
|
||||
"""
|
||||
SELECT award_type, period_key, matches
|
||||
FROM dm_player_awards
|
||||
"""
|
||||
).fetchall()
|
||||
finally:
|
||||
l3.close()
|
||||
self.assertGreater(len(rows), 0)
|
||||
seen = set()
|
||||
for award_type, period_key, matches in rows:
|
||||
with self.subTest(award_type=award_type, period_key=period_key):
|
||||
self.assertGreaterEqual(matches, minimums[award_type])
|
||||
self.assertNotIn((award_type, period_key), seen)
|
||||
seen.add((award_type, period_key))
|
||||
|
||||
def test_seasons_only_include_team_matches(self):
|
||||
l2 = sqlite3.connect(Config.DB_L2_PATH)
|
||||
l3 = sqlite3.connect(Config.DB_L3_PATH)
|
||||
web = sqlite3.connect(Config.DB_WEB_PATH)
|
||||
try:
|
||||
roster_ids = json.loads(
|
||||
web.execute(
|
||||
"""
|
||||
SELECT player_ids_json
|
||||
FROM team_lineups
|
||||
WHERE is_active = 1
|
||||
"""
|
||||
).fetchone()[0]
|
||||
)
|
||||
placeholders = ','.join('?' for _ in roster_ids)
|
||||
expected = {
|
||||
str(row[0]): row[1]
|
||||
for row in l2.execute(
|
||||
f"""
|
||||
SELECT
|
||||
strftime('%Y', m.start_time, 'unixepoch') AS season,
|
||||
COUNT(DISTINCT grouped.match_id)
|
||||
FROM (
|
||||
SELECT
|
||||
match_id,
|
||||
CASE
|
||||
WHEN group_id IN (1, 2) THEN group_id
|
||||
WHEN team_id IN (1, 2) THEN team_id
|
||||
END AS team_key
|
||||
FROM fact_match_players
|
||||
WHERE steam_id_64 IN ({placeholders})
|
||||
GROUP BY match_id, team_key
|
||||
HAVING COUNT(DISTINCT steam_id_64) >= 2
|
||||
AND team_key IS NOT NULL
|
||||
) grouped
|
||||
JOIN fact_matches m ON m.match_id = grouped.match_id
|
||||
GROUP BY strftime('%Y', m.start_time, 'unixepoch')
|
||||
""",
|
||||
roster_ids,
|
||||
)
|
||||
}
|
||||
actual = {
|
||||
row[0]: row[1]
|
||||
for row in l3.execute(
|
||||
'SELECT season_key, matches FROM dm_team_season_stats'
|
||||
)
|
||||
}
|
||||
finally:
|
||||
l2.close()
|
||||
l3.close()
|
||||
web.close()
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_roster_version_snapshot_closes_previous_version(self):
|
||||
from web.services.roster_version_service import RosterVersionService
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
web_path = Path(temp_dir) / 'Web.sqlite'
|
||||
l2_path = Path(temp_dir) / 'L2.sqlite'
|
||||
with sqlite3.connect(str(web_path)) as db:
|
||||
with open(Config.DB_WEB_SCHEMA_PATH, 'r', encoding='utf-8') as schema:
|
||||
db.executescript(schema.read())
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO team_lineups (
|
||||
name, player_ids_json, is_active
|
||||
) VALUES ('Test', '["1", "2"]', 1)
|
||||
"""
|
||||
)
|
||||
with sqlite3.connect(str(l2_path)) as db:
|
||||
db.executescript(
|
||||
"""
|
||||
CREATE TABLE fact_matches (
|
||||
match_id TEXT PRIMARY KEY,
|
||||
start_time INTEGER
|
||||
);
|
||||
CREATE TABLE fact_match_players (
|
||||
match_id TEXT,
|
||||
steam_id_64 TEXT
|
||||
);
|
||||
INSERT INTO fact_matches VALUES ('m1', 100);
|
||||
INSERT INTO fact_match_players VALUES ('m1', '1');
|
||||
INSERT INTO fact_match_players VALUES ('m1', '2');
|
||||
"""
|
||||
)
|
||||
|
||||
first_id = RosterVersionService.ensure_initial_version(
|
||||
str(web_path),
|
||||
str(l2_path),
|
||||
)
|
||||
second_id, created = RosterVersionService.snapshot_roster(
|
||||
['1', '3'],
|
||||
effective_from=200,
|
||||
web_db_path=str(web_path),
|
||||
)
|
||||
RosterVersionService.update_current_member_role(
|
||||
'1',
|
||||
'starter',
|
||||
web_db_path=str(web_path),
|
||||
)
|
||||
self.assertTrue(created)
|
||||
self.assertNotEqual(first_id, second_id)
|
||||
|
||||
with sqlite3.connect(str(web_path)) as db:
|
||||
old = db.execute(
|
||||
"""
|
||||
SELECT is_current, effective_to
|
||||
FROM team_roster_versions
|
||||
WHERE id = ?
|
||||
""",
|
||||
[first_id],
|
||||
).fetchone()
|
||||
current_ids = [
|
||||
row[0] for row in db.execute(
|
||||
"""
|
||||
SELECT steam_id_64
|
||||
FROM team_roster_members
|
||||
WHERE roster_version_id = ?
|
||||
ORDER BY position_order
|
||||
""",
|
||||
[second_id],
|
||||
)
|
||||
]
|
||||
role = db.execute(
|
||||
"""
|
||||
SELECT member_role
|
||||
FROM team_roster_members
|
||||
WHERE roster_version_id = ? AND steam_id_64 = '1'
|
||||
""",
|
||||
[second_id],
|
||||
).fetchone()[0]
|
||||
self.assertEqual(old, (0, 199))
|
||||
self.assertEqual(current_ids, ['1', '3'])
|
||||
self.assertEqual(role, 'starter')
|
||||
|
||||
def test_team_marts_cover_active_roster_combinations(self):
|
||||
import json
|
||||
from math import comb
|
||||
|
||||
web = sqlite3.connect(Config.DB_WEB_PATH)
|
||||
l3 = sqlite3.connect(Config.DB_L3_PATH)
|
||||
try:
|
||||
roster_ids = json.loads(
|
||||
web.execute(
|
||||
"""
|
||||
SELECT player_ids_json
|
||||
FROM team_lineups
|
||||
WHERE is_active = 1
|
||||
"""
|
||||
).fetchone()[0]
|
||||
)
|
||||
duo_count = l3.execute(
|
||||
'SELECT COUNT(*) FROM dm_duo_stats'
|
||||
).fetchone()[0]
|
||||
lineup_sizes = {
|
||||
row[0]: row[1]
|
||||
for row in l3.execute(
|
||||
"""
|
||||
SELECT player_count, COUNT(*)
|
||||
FROM dm_lineup_stats
|
||||
GROUP BY player_count
|
||||
"""
|
||||
)
|
||||
}
|
||||
finally:
|
||||
web.close()
|
||||
l3.close()
|
||||
|
||||
self.assertEqual(duo_count, comb(len(roster_ids), 2))
|
||||
self.assertTrue(all(size in lineup_sizes for size in (2, 3, 4, 5)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user