blob: 2aeffdbe5f33ca2d03af8958ed3b529a9eed2626 [file] [log] [blame]
rootff0769a2025-05-18 17:24:41 +00001package trackertest;
rootd4959a82025-05-27 07:07:37 +00002import java.io.File;
rootff0769a2025-05-18 17:24:41 +00003import java.util.Collection;
4import java.util.HashMap;
5import java.util.List;
6import java.util.Map;
7import java.util.Random;
rootf35409f2025-05-19 04:41:57 +00008import java.util.UUID;
rootff0769a2025-05-18 17:24:41 +00009import java.util.stream.Collectors;
rootf35409f2025-05-19 04:41:57 +000010import java.util.stream.IntStream;
Raveraae06122025-06-05 08:13:35 +000011
rootff0769a2025-05-18 17:24:41 +000012import javax.persistence.EntityManager;
13import javax.persistence.EntityManagerFactory;
rootf35409f2025-05-19 04:41:57 +000014import javax.persistence.EntityTransaction;
rootff0769a2025-05-18 17:24:41 +000015import javax.persistence.Persistence;
Raveraae06122025-06-05 08:13:35 +000016
rootff0769a2025-05-18 17:24:41 +000017import org.junit.jupiter.api.AfterAll;
18import org.junit.jupiter.api.Assertions;
19import org.junit.jupiter.api.BeforeAll;
20import org.junit.jupiter.api.DynamicTest;
21import org.junit.jupiter.api.TestFactory;
Raveraae06122025-06-05 08:13:35 +000022
rootd4959a82025-05-27 07:07:37 +000023import entity.Seed;
rootf35409f2025-05-19 04:41:57 +000024import entity.TransRecord;
rootff0769a2025-05-18 17:24:41 +000025import entity.config;
26import tracker.Tracker;
rootff0769a2025-05-18 17:24:41 +000027public class TrackerTest {
28 private static EntityManagerFactory emf;
29 private static EntityManager em;
30 private static List<String> userIds;
31 private static Map<String, Long> originalUploads;
32 private static Tracker tracker;
TRM-codingd5de51e2025-06-08 03:27:01 +080033 private static List<String> infoHashes;
rootff0769a2025-05-18 17:24:41 +000034 @BeforeAll
35 static void setup() throws Exception {
36 // 强制加载 MySQL 驱动,否则无法建立连接
37 Class.forName("com.mysql.cj.jdbc.Driver");
38 config cfg = new config();
39 Map<String,Object> props = new HashMap<>();
40 // 添加时区和 SSL 参数
41 String jdbcUrl = String.format(
42 "jdbc:mysql://%s/%s?useSSL=false&serverTimezone=UTC",
43 cfg.SqlURL, cfg.TestDatabase);
44 props.put("javax.persistence.jdbc.url", jdbcUrl);
45 props.put("javax.persistence.jdbc.user", cfg.SqlUsername);
46 props.put("javax.persistence.jdbc.password", cfg.SqlPassword);
47 props.put("javax.persistence.jdbc.driver", "com.mysql.cj.jdbc.Driver");
48 emf = Persistence.createEntityManagerFactory("myPersistenceUnit", props);
49 em = emf.createEntityManager();
50 // 使用简单实体名而非带包名前缀
51 userIds = em.createQuery(
52 "select u.userid from UserPT u", String.class
53 ).getResultList();
rootff0769a2025-05-18 17:24:41 +000054 // 保存初始 upload 值
55 originalUploads = new HashMap<>();
56 for (String uid : userIds) {
57 Long up = em.createQuery(
58 "select u.upload from UserPT u where u.userid = :uid", Long.class
59 ).setParameter("uid", uid)
60 .getSingleResult();
61 originalUploads.put(uid, up != null ? up : 0L);
62 }
TRM-codingd5de51e2025-06-08 03:27:01 +080063 // fetch real infoHash values
64 infoHashes = em.createQuery(
65 "select s.infoHash from SeedHash s", String.class
66 ).getResultList();
rootff0769a2025-05-18 17:24:41 +000067 tracker = new Tracker(emf);
68 }
rootff0769a2025-05-18 17:24:41 +000069 @AfterAll
70 static void teardown() {
rootd4959a82025-05-27 07:07:37 +000071 // 清理:删除测试过程中保存的所有 torrent 文件
72 File storageDir = new File(config.TORRENT_STORAGE_DIR);
73 if (storageDir.exists() && storageDir.isDirectory()) {
74 File[] files = storageDir.listFiles();
75 if (files != null) {
76 for (File f : files) {
77 if (f.isFile()) {
78 f.delete();
79 }
80 }
81 }
82 }
rootff0769a2025-05-18 17:24:41 +000083 if (em != null && em.isOpen()) em.close();
84 if (emf != null && emf.isOpen()) emf.close();
85 }
TRM-codingd5de51e2025-06-08 03:27:01 +080086 /*
rootff0769a2025-05-18 17:24:41 +000087 @TestFactory
88 Collection<DynamicTest> testAddUpLoad() {
89 Random rnd = new Random();
TRM-codingd5de51e2025-06-08 03:27:01 +080090 String ih = infoHashes.get(0);
rootff0769a2025-05-18 17:24:41 +000091 return userIds.stream()
92 .map(uid -> DynamicTest.dynamicTest("AddUpLoad for user " + uid, () -> {
TRM-codingd5de51e2025-06-08 03:27:01 +080093 EntityTransaction tx = em.getTransaction();
94 tx.begin();
95 try {
96 // 获取该用户当前的总上传量
97 Long currentUserUpload = em.createQuery(
98 "SELECT COALESCE(u.upload,0) FROM UserPT u WHERE u.userid = :uid", Long.class
99 ).setParameter("uid", uid).getSingleResult();
100
101 // 获取该用户在该种子上的当前上传量
102 Long currentSeedUpload = em.createQuery(
103 "SELECT COALESCE(SUM(t.upload),0) FROM TransRecord t WHERE t.uploaduserid = :uid AND t.seedid = " +
104 "(SELECT s.seedId FROM SeedHash s WHERE s.infoHash = :ih)", Long.class
105 ).setParameter("uid", uid).setParameter("ih", ih).getSingleResult();
106
107 int delta = rnd.nextInt(1000) + 1;
108 long newSeedTotal = currentSeedUpload + delta;
109
110 Assertions.assertFalse(tracker.AddUpLoad(uid, (int)newSeedTotal, ih),
111 "AddUpLoad should return false on successful operation");
112 em.clear();
113
114 // commit & restart test TX so we see the data that tracker committed
115 tx.commit();
116 em.clear();
117 tx.begin();
118
119 // 验证 UserPT.upload 是否等于 TransRecord 表中该用户的实际总和
120 Long actualTransRecordSum = em.createQuery(
121 "SELECT COALESCE(SUM(t.upload),0) FROM TransRecord t WHERE t.uploaduserid = :uid", Long.class
122 ).setParameter("uid", uid).getSingleResult();
123
124 Long userPTUpload = em.createQuery(
125 "SELECT u.upload FROM UserPT u WHERE u.userid = :uid", Long.class
126 ).setParameter("uid", uid).getSingleResult();
127
128 Assertions.assertEquals(actualTransRecordSum, userPTUpload,
129 "UserPT.upload should equal sum of TransRecord.upload for this user");
130 Assertions.assertEquals(currentUserUpload + delta, userPTUpload.longValue(),
131 "User total upload should increase by delta");
132 } finally {
133 tx.rollback();
134 em.clear();
135 }
rootff0769a2025-05-18 17:24:41 +0000136 }))
137 .collect(Collectors.toList());
138 }
TRM-codingd5de51e2025-06-08 03:27:01 +0800139 */
140
141
142 // @TestFactory
143 // Collection<DynamicTest> testReduceUpLoad() {
144 // Random rnd = new Random();
145 // String ih = infoHashes.get(0); // use same infoHash as other tests
146 // return userIds.stream()
147 // .map(uid -> DynamicTest.dynamicTest("ReduceUpLoad for user " + uid, () -> {
148 // long before = originalUploads.get(uid);
149 // int max = (int)Math.min(before, 1000);
150 // int delta = max > 0 ? rnd.nextInt(max) + 1 : 0;
151 // if (delta == 0) return; // 无可减量时跳过
152 // // ReduceUpLoad 前打印
153 // System.out.println("Running ReduceUpLoad assert for user=" + uid + ", delta=" + delta);
154 // Assertions.assertFalse(tracker.ReduceUpLoad(uid, delta));
155 // System.out.println("ReduceUpLoad assert passed for user=" + uid);
156 // Long after = em.createQuery(
157 // "select u.upload from UserPT u where u.userid = :uid", Long.class
158 // ).setParameter("uid", uid)
159 // .getSingleResult();
160 // // 减少后值断言前打印
161 // System.out.println("Running post-reduce-value assert for user=" + uid);
162 // Assertions.assertEquals(before - delta, after);
163 // System.out.println("Post-reduce-value assert passed for user=" + uid);
164 // // 回滚 AddUpLoad 前打印
165 // System.out.println("Running rollback AddUpLoad assert for user=" + uid + ", delta=" + delta);
166 // Assertions.assertFalse(tracker.AddUpLoad(uid, (int)before, ih));
167 // System.out.println("Rollback AddUpLoad assert passed for user=" + uid);
168 // }))
169 // .collect(Collectors.toList());
170 // }
171
172 /*
rootf35409f2025-05-19 04:41:57 +0000173 @TestFactory
174 Collection<DynamicTest> testAddDownload() {
175 Random rnd = new Random();
TRM-codingd5de51e2025-06-08 03:27:01 +0800176 String ih = infoHashes.get(0);
rootf35409f2025-05-19 04:41:57 +0000177 return userIds.stream()
178 .map(uid -> DynamicTest.dynamicTest("AddDownload for user " + uid, () -> {
TRM-codingd5de51e2025-06-08 03:27:01 +0800179 EntityTransaction tx = em.getTransaction();
180 tx.begin();
181 try {
182 // 获取该用户当前的总下载量
183 Long currentUserDownload = em.createQuery(
184 "SELECT COALESCE(u.download,0) FROM UserPT u WHERE u.userid = :uid", Long.class
185 ).setParameter("uid", uid).getSingleResult();
186
187 // 获取该用户在该种子上的当前下载量
188 Long currentSeedDownload = em.createQuery(
189 "SELECT COALESCE(SUM(t.download),0) FROM TransRecord t WHERE t.downloaduserid = :uid AND t.seedid = " +
190 "(SELECT s.seedId FROM SeedHash s WHERE s.infoHash = :ih)", Long.class
191 ).setParameter("uid", uid).setParameter("ih", ih).getSingleResult();
192
193 int delta = rnd.nextInt(1000) + 1;
194 long newSeedTotal = currentSeedDownload + delta;
195
196 Assertions.assertFalse(tracker.AddDownload(uid, (int)newSeedTotal, ih),
197 "AddDownload should return false on successful operation");
198 em.clear();
199
200 // commit & restart test TX so we see the data that tracker committed
201 tx.commit();
202 em.clear();
203 tx.begin();
204
205 // 验证 UserPT.download 是否等于 TransRecord 表中该用户的实际总和
206 Long actualTransRecordSum = em.createQuery(
207 "SELECT COALESCE(SUM(t.download),0) FROM TransRecord t WHERE t.downloaduserid = :uid", Long.class
208 ).setParameter("uid", uid).getSingleResult();
209
210 Long userPTDownload = em.createQuery(
211 "SELECT u.download FROM UserPT u WHERE u.userid = :uid", Long.class
212 ).setParameter("uid", uid).getSingleResult();
213
214 Assertions.assertEquals(actualTransRecordSum, userPTDownload,
215 "UserPT.download should equal sum of TransRecord.download for this user");
216
217 // 验证用户总下载量增加了预期的delta
218 Assertions.assertEquals(currentUserDownload + delta, userPTDownload.longValue(),
219 "User total download should increase by delta");
220 } finally {
221 tx.rollback();
222 em.clear();
223 }
rootf35409f2025-05-19 04:41:57 +0000224 }))
225 .collect(Collectors.toList());
226 }
TRM-codingd5de51e2025-06-08 03:27:01 +0800227 */
rootf35409f2025-05-19 04:41:57 +0000228 @TestFactory
229 Collection<DynamicTest> testReduceDownload() {
230 Random rnd = new Random();
231 return userIds.stream()
232 .map(uid -> DynamicTest.dynamicTest("ReduceDownload for user " + uid, () -> {
233 Long before = em.createQuery(
TRM-codingd5de51e2025-06-08 03:27:01 +0800234 "SELECT u.download FROM UserPT u WHERE u.userid = :uid", Long.class
rootf35409f2025-05-19 04:41:57 +0000235 ).setParameter("uid", uid).getSingleResult();
236 before = before != null ? before : 0L;
237 int max = before.intValue();
238 int delta = max > 0 ? rnd.nextInt(max) + 1 : 0;
239 if (delta == 0) return;
TRM-codingd5de51e2025-06-08 03:27:01 +0800240
rootf35409f2025-05-19 04:41:57 +0000241 System.out.println("Running ReduceDownload assert for user=" + uid + ", delta=" + delta);
242 Assertions.assertFalse(tracker.ReduceDownload(uid, delta));
TRM-codingd5de51e2025-06-08 03:27:01 +0800243 em.clear();
244
rootf35409f2025-05-19 04:41:57 +0000245 Long after = em.createQuery(
TRM-codingd5de51e2025-06-08 03:27:01 +0800246 "SELECT u.download FROM UserPT u WHERE u.userid = :uid", Long.class
rootf35409f2025-05-19 04:41:57 +0000247 ).setParameter("uid", uid).getSingleResult();
248 System.out.println("Running post-reduce-download-value assert for user=" + uid);
TRM-codingd5de51e2025-06-08 03:27:01 +0800249 Assertions.assertEquals(before - delta, after.longValue());
250
rootf35409f2025-05-19 04:41:57 +0000251 System.out.println("Running rollback AddDownload assert for user=" + uid + ", delta=" + delta);
TRM-codingd5de51e2025-06-08 03:27:01 +0800252 // 使用有效的 infoHash
253 Assertions.assertFalse(
254 tracker.AddDownload(uid, before.intValue(), infoHashes.get(0))
255 );
rootf35409f2025-05-19 04:41:57 +0000256 }))
257 .collect(Collectors.toList());
258 }
rootf35409f2025-05-19 04:41:57 +0000259 @TestFactory
260 Collection<DynamicTest> testAddMagic() {
261 Random rnd = new Random();
262 return userIds.stream()
263 .map(uid -> DynamicTest.dynamicTest("AddMagic for user " + uid, () -> {
264 int delta = rnd.nextInt(1000) + 1;
265 Integer before = em.createQuery(
266 "select u.magic from UserPT u where u.userid = :uid", Integer.class
267 ).setParameter("uid", uid).getSingleResult();
268 before = before != null ? before : 0;
rootf35409f2025-05-19 04:41:57 +0000269 System.out.println("Running AddMagic assert for user=" + uid + ", delta=" + delta);
270 Assertions.assertFalse(tracker.AddMagic(uid, delta));
271 em.clear();
rootf35409f2025-05-19 04:41:57 +0000272 Integer after = em.createQuery(
273 "select u.magic from UserPT u where u.userid = :uid", Integer.class
274 ).setParameter("uid", uid).getSingleResult();
275 System.out.println("Running magic-value assert for user=" + uid);
TRM-codingd5de51e2025-06-08 03:27:01 +0800276 Assertions.assertEquals((Integer)(before + delta), after);
rootf35409f2025-05-19 04:41:57 +0000277 System.out.println("Running rollback ReduceMagic assert for user=" + uid + ", delta=" + delta);
278 Assertions.assertFalse(tracker.ReduceMagic(uid, delta));
279 }))
280 .collect(Collectors.toList());
281 }
rootf35409f2025-05-19 04:41:57 +0000282 @TestFactory
283 Collection<DynamicTest> testReduceMagic() {
284 Random rnd = new Random();
285 return userIds.stream()
286 .map(uid -> DynamicTest.dynamicTest("ReduceMagic for user " + uid, () -> {
287 Integer before = em.createQuery(
288 "select u.magic from UserPT u where u.userid = :uid", Integer.class
289 ).setParameter("uid", uid).getSingleResult();
290 before = before != null ? before : 0;
291 int max = before.intValue();
292 int delta = max > 0 ? rnd.nextInt(max) + 1 : 0;
293 if (delta == 0) return;
rootf35409f2025-05-19 04:41:57 +0000294 System.out.println("Running ReduceMagic assert for user=" + uid + ", delta=" + delta);
295 Assertions.assertFalse(tracker.ReduceMagic(uid, delta));
TRM-codingd5de51e2025-06-08 03:27:01 +0800296 em.clear();
rootf35409f2025-05-19 04:41:57 +0000297 Integer after = em.createQuery(
298 "select u.magic from UserPT u where u.userid = :uid", Integer.class
299 ).setParameter("uid", uid).getSingleResult();
300 System.out.println("Running post-reduce-magic-value assert for user=" + uid);
TRM-codingd5de51e2025-06-08 03:27:01 +0800301 Assertions.assertEquals((Integer)(before - delta), after);
rootf35409f2025-05-19 04:41:57 +0000302 System.out.println("Running rollback AddMagic assert for user=" + uid + ", delta=" + delta);
303 Assertions.assertFalse(tracker.AddMagic(uid, delta));
304 }))
305 .collect(Collectors.toList());
306 }
rootf35409f2025-05-19 04:41:57 +0000307 @TestFactory
308 Collection<DynamicTest> testAddRecord() {
309 Random rnd = new Random();
310 // 取所有 seed_id 用于外键测试
311 List<String> seedIds = em.createQuery(
312 "select s.seedid from Seed s", String.class
313 ).getResultList();
314 // 若无可用 seedId 则跳过此组测试,避免 rnd.nextInt(0) 抛错
315 // if (seedIds.isEmpty()) {
316 // return Collections.emptyList();
317 // }
318 return IntStream.range(0, 10)
319 .mapToObj(i -> DynamicTest.dynamicTest("AddRecord test #" + i, () -> {
320 // 随机构造 TransRecord
321 String uploaderId = userIds.get(rnd.nextInt(userIds.size()));
322 String downloaderId = userIds.get(rnd.nextInt(userIds.size()));
323 String seedId = seedIds.get(rnd.nextInt(seedIds.size()));
324 String taskId = UUID.randomUUID().toString();
rootf35409f2025-05-19 04:41:57 +0000325 TransRecord rd = new TransRecord();
326 rd.taskid = taskId;
327 rd.uploaduserid = uploaderId;
328 rd.downloaduserid = downloaderId;
329 rd.seedid = seedId;
TRM-codingd5de51e2025-06-08 03:27:01 +0800330 rd.upload = (long) rnd.nextInt(10000);
331 rd.download = (long) rnd.nextInt(10000);
332 rd.maxupload = rd.upload + (long) rnd.nextInt(10000);
333 rd.maxdownload = rd.download + (long) rnd.nextInt(10000);
rootf35409f2025-05-19 04:41:57 +0000334 // 调用待测方法
335 int ret = tracker.AddRecord(rd);
336 Assertions.assertTrue(ret > 0, "返回值应为新记录主键");
rootf35409f2025-05-19 04:41:57 +0000337 // 验证已插入
TRM-codingd5de51e2025-06-08 03:27:01 +0800338 TransRecord fetched = em.find(TransRecord.class, rd.taskid);
339
340 Assertions.assertNotNull(fetched, "查询应返回非空实体");
341 Assertions.assertEquals(rd.upload, fetched.upload);
342 Assertions.assertEquals(rd.download, fetched.download);
343 Assertions.assertEquals(rd.maxupload, fetched.maxupload);
344 Assertions.assertEquals(rd.maxdownload, fetched.maxdownload);
rootff0769a2025-05-18 17:24:41 +0000345 }))
346 .collect(Collectors.toList());
347 }
rootd4959a82025-05-27 07:07:37 +0000348 @TestFactory
349 Collection<DynamicTest> testSaveTorrent() {
350 List<String> seedIds = em.createQuery(
351 "select s.seedid from Seed s", String.class
352 ).getResultList();
353 return seedIds.stream()
354 .map(sid -> DynamicTest.dynamicTest("SaveTorrent for seed " + sid, () -> {
355 // 准备一个临时空文件
356 File temp = File.createTempFile(sid + "_test", ".torrent");
357 // 调用 SaveTorrent
358 int ret = tracker.SaveTorrent(sid, temp);
359 Assertions.assertEquals(0, ret, "SaveTorrent 应返回 0");
rootd4959a82025-05-27 07:07:37 +0000360 // 验证文件已按约定存储
361 File stored = new File(config.TORRENT_STORAGE_DIR,
362 sid + "_" + temp.getName());
363 Assertions.assertTrue(stored.exists(), "存储文件应存在");
rootd4959a82025-05-27 07:07:37 +0000364 // 验证数据库中 url 字段已更新
365 em.clear();
366 Seed seed = em.find(Seed.class, sid);
367 // 将 seed.url 转为 File 再取绝对路径进行比较
368 Assertions.assertEquals(
369 stored.getAbsolutePath(),
370 new File(seed.url).getAbsolutePath(),
371 "Seed.url 应更新为存储路径"
372 );
rootd4959a82025-05-27 07:07:37 +0000373 // 清理测试文件
374 stored.delete();
375 temp.delete();
376 }))
377 .collect(Collectors.toList());
378 }
rootd4959a82025-05-27 07:07:37 +0000379 @TestFactory
380 Collection<DynamicTest> testGetTTorent() {
381 // 拿到所有 seedid
382 List<String> seedIds = em.createQuery(
383 "select s.seedid from Seed s", String.class
384 ).getResultList();
385 return seedIds.stream()
386 .map(sid -> DynamicTest.dynamicTest("GetTTorent for seed " + sid, () -> {
387 // 准备一个临时空文件并 SaveTorrent
388 File temp = File.createTempFile(sid + "_test", ".torrent");
389 int saveRet = tracker.SaveTorrent(sid, temp);
390 Assertions.assertEquals(0, saveRet, "SaveTorrent 应返回 0");
rootd4959a82025-05-27 07:07:37 +0000391 // 刷新上下文并取回更新后的 seed 实体
392 em.clear();
393 Seed seed = em.find(Seed.class, sid);
394 Assertions.assertNotNull(seed.url, "Seed.url 应已被更新");
rootd4959a82025-05-27 07:07:37 +0000395 // 调用 GetTTorent 并断言路径一致
396 String uid = userIds.get(0), ip = "127.0.0.1";
Raveraae06122025-06-05 08:13:35 +0000397 File ret = tracker.GetTTorent(sid, uid);
rootd4959a82025-05-27 07:07:37 +0000398 File expected = new File(seed.url);
399 Assertions.assertNotNull(ret, "应返回文件对象");
400 Assertions.assertEquals(
401 expected.getAbsolutePath(),
402 ret.getAbsolutePath(),
403 "返回文件路径应与Seed.url一致"
404 );
rootd4959a82025-05-27 07:07:37 +0000405 // 清理:删掉本地文件,回滚 DB url 字段,删掉 temp
406 expected.delete();
407 EntityTransaction tx = em.getTransaction();
408 tx.begin();
409 seed.url = null;
410 em.merge(seed);
411 tx.commit();
412 temp.delete();
413 }))
414 .collect(Collectors.toList());
415 }
TRM-codingd5de51e2025-06-08 03:27:01 +0800416
417}