// // JXPlaybackProgressStorageTests.m // AICityTests // // Feature: 003-ios-api-https // 播放进度本地存储单元测试 // #import #import "JXPlaybackProgressStorage.h" #import "JXPlaybackProgressModel.h" @interface JXPlaybackProgressStorageTests : XCTestCase @property (nonatomic, strong) JXPlaybackProgressStorage *storage; @end @implementation JXPlaybackProgressStorageTests - (void)setUp { [super setUp]; self.storage = [JXPlaybackProgressStorage sharedStorage]; // 清空所有数据 [self.storage clearAll]; } - (void)tearDown { // 清理测试数据 [self.storage clearAll]; [super tearDown]; } #pragma mark - 基础功能测试 - (void)testSaveAndGetProgress { // Given NSString *episodeId = @"test_ep_001"; NSString *dramaId = @"test_drama_001"; int64_t position = 30000; int64_t duration = 120000; // When XCTestExpectation *expectation = [self expectationWithDescription:@"Save progress"]; [self.storage saveProgressWithEpisodeId:episodeId dramaId:dramaId position:position duration:duration isCompleted:NO synced:NO completion:^(BOOL success, NSError *error) { XCTAssertTrue(success, @"保存应该成功"); XCTAssertNil(error, @"不应该有错误"); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; // Then JXPlaybackProgressModel *progress = [self.storage getProgressWithEpisodeId:episodeId]; XCTAssertNotNil(progress, @"应该能获取到进度"); XCTAssertEqualObjects(progress.episodeId, episodeId); XCTAssertEqualObjects(progress.dramaId, dramaId); XCTAssertEqual(progress.position, position); XCTAssertEqual(progress.duration, duration); XCTAssertEqualWithAccuracy(progress.progress, 0.25f, 0.01f); // 30000/120000 = 0.25 XCTAssertFalse(progress.isCompleted); XCTAssertFalse(progress.synced); } - (void)testUpdateExistingProgress { // Given NSString *episodeId = @"test_ep_002"; XCTestExpectation *expectation1 = [self expectationWithDescription:@"Save initial"]; [self.storage saveProgressWithEpisodeId:episodeId dramaId:@"test_drama_002" position:10000 duration:120000 isCompleted:NO synced:NO completion:^(BOOL success, NSError *error) { [expectation1 fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; // When - 更新进度 XCTestExpectation *expectation2 = [self expectationWithDescription:@"Update progress"]; [self.storage saveProgressWithEpisodeId:episodeId dramaId:@"test_drama_002" position:60000 duration:120000 isCompleted:NO synced:YES completion:^(BOOL success, NSError *error) { [expectation2 fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; // Then JXPlaybackProgressModel *progress = [self.storage getProgressWithEpisodeId:episodeId]; XCTAssertEqual(progress.position, 60000); XCTAssertEqualWithAccuracy(progress.progress, 0.5f, 0.01f); XCTAssertTrue(progress.synced); } - (void)testGetProgressByDrama { // Given NSString *dramaId = @"test_drama_003"; XCTestExpectation *expectation = [self expectationWithDescription:@"Save multiple"]; [self.storage saveProgressWithEpisodeId:@"ep001" dramaId:dramaId position:10000 duration:120000 isCompleted:NO synced:NO completion:nil]; [self.storage saveProgressWithEpisodeId:@"ep002" dramaId:dramaId position:20000 duration:120000 isCompleted:NO synced:NO completion:nil]; [self.storage saveProgressWithEpisodeId:@"ep003" dramaId:@"other_drama" position:30000 duration:120000 isCompleted:NO synced:NO completion:^(BOOL success, NSError *error) { [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; // When NSArray *progresses = [self.storage getProgressByDramaId:dramaId]; // Then XCTAssertEqual(progresses.count, 2, @"应该有2条记录"); BOOL hasEp001 = NO; BOOL hasEp002 = NO; for (JXPlaybackProgressModel *p in progresses) { if ([p.episodeId isEqualToString:@"ep001"]) hasEp001 = YES; if ([p.episodeId isEqualToString:@"ep002"]) hasEp002 = YES; } XCTAssertTrue(hasEp001 && hasEp002, @"应该包含ep001和ep002"); } - (void)testGetUnsyncedProgresses { // Given XCTestExpectation *expectation = [self expectationWithDescription:@"Save progresses"]; [self.storage saveProgressWithEpisodeId:@"ep001" dramaId:@"drama" position:10000 duration:120000 isCompleted:NO synced:YES completion:nil]; [self.storage saveProgressWithEpisodeId:@"ep002" dramaId:@"drama" position:20000 duration:120000 isCompleted:NO synced:NO completion:nil]; [self.storage saveProgressWithEpisodeId:@"ep003" dramaId:@"drama" position:30000 duration:120000 isCompleted:NO synced:NO completion:^(BOOL success, NSError *error) { [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; // When NSArray *unsyncedProgresses = [self.storage getUnsyncedProgresses]; // Then XCTAssertEqual(unsyncedProgresses.count, 2, @"应该有2条未同步记录"); for (JXPlaybackProgressModel *p in unsyncedProgresses) { XCTAssertFalse(p.synced, @"所有记录都应该是未同步的"); } } - (void)testMarkAsSynced { // Given NSString *episodeId = @"test_ep_004"; XCTestExpectation *expectation = [self expectationWithDescription:@"Save progress"]; [self.storage saveProgressWithEpisodeId:episodeId dramaId:@"drama" position:10000 duration:120000 isCompleted:NO synced:NO completion:^(BOOL success, NSError *error) { [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; // When [self.storage markAsSyncedWithEpisodeId:episodeId]; // Then JXPlaybackProgressModel *progress = [self.storage getProgressWithEpisodeId:episodeId]; XCTAssertTrue(progress.synced, @"应该被标记为已同步"); } - (void)testDeleteProgress { // Given NSString *episodeId = @"test_ep_005"; XCTestExpectation *expectation = [self expectationWithDescription:@"Save progress"]; [self.storage saveProgressWithEpisodeId:episodeId dramaId:@"drama" position:10000 duration:120000 isCompleted:NO synced:NO completion:^(BOOL success, NSError *error) { [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertNotNil([self.storage getProgressWithEpisodeId:episodeId], @"保存后应该存在"); // When [self.storage deleteProgressWithEpisodeId:episodeId]; // Then JXPlaybackProgressModel *progress = [self.storage getProgressWithEpisodeId:episodeId]; XCTAssertNil(progress, @"删除后应该不存在"); } - (void)testDeleteProgressByDrama { // Given NSString *dramaId = @"test_drama_006"; XCTestExpectation *expectation = [self expectationWithDescription:@"Save progresses"]; [self.storage saveProgressWithEpisodeId:@"ep001" dramaId:dramaId position:10000 duration:120000 isCompleted:NO synced:NO completion:nil]; [self.storage saveProgressWithEpisodeId:@"ep002" dramaId:dramaId position:20000 duration:120000 isCompleted:NO synced:NO completion:^(BOOL success, NSError *error) { [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; // When [self.storage deleteProgressByDramaId:dramaId]; // Then NSArray *progresses = [self.storage getProgressByDramaId:dramaId]; XCTAssertEqual(progresses.count, 0, @"删除后应该没有记录"); } #pragma mark - 边界值测试 - (void)testProgressCalculation { XCTestExpectation *expectation = [self expectationWithDescription:@"Save progresses"]; // Test case 1: 25% progress [self.storage saveProgressWithEpisodeId:@"ep_25" dramaId:@"drama" position:30000 duration:120000 isCompleted:NO synced:NO completion:nil]; // Test case 2: 50% progress [self.storage saveProgressWithEpisodeId:@"ep_50" dramaId:@"drama" position:60000 duration:120000 isCompleted:NO synced:NO completion:nil]; // Test case 3: 95% progress (completed) [self.storage saveProgressWithEpisodeId:@"ep_95" dramaId:@"drama" position:114000 duration:120000 isCompleted:YES synced:NO completion:^(BOOL success, NSError *error) { [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; // Verify JXPlaybackProgressModel *progress25 = [self.storage getProgressWithEpisodeId:@"ep_25"]; XCTAssertEqualWithAccuracy(progress25.progress, 0.25f, 0.01f); JXPlaybackProgressModel *progress50 = [self.storage getProgressWithEpisodeId:@"ep_50"]; XCTAssertEqualWithAccuracy(progress50.progress, 0.5f, 0.01f); JXPlaybackProgressModel *progress95 = [self.storage getProgressWithEpisodeId:@"ep_95"]; XCTAssertEqualWithAccuracy(progress95.progress, 0.95f, 0.01f); XCTAssertTrue(progress95.isCompleted); } - (void)testEdgeCases { XCTestExpectation *expectation = [self expectationWithDescription:@"Save edge cases"]; // Test case 1: Zero duration [self.storage saveProgressWithEpisodeId:@"ep_zero" dramaId:@"drama" position:0 duration:0 isCompleted:NO synced:NO completion:nil]; // Test case 2: Position exceeds duration [self.storage saveProgressWithEpisodeId:@"ep_exceed" dramaId:@"drama" position:150000 duration:120000 isCompleted:NO synced:NO completion:^(BOOL success, NSError *error) { [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; // Verify JXPlaybackProgressModel *progressZero = [self.storage getProgressWithEpisodeId:@"ep_zero"]; XCTAssertEqual(progressZero.progress, 0.0f); JXPlaybackProgressModel *progressExceed = [self.storage getProgressWithEpisodeId:@"ep_exceed"]; XCTAssertLessThanOrEqual(progressExceed.progress, 1.0f); } #pragma mark - 性能测试 - (void)testPerformanceSaveMultipleProgresses { [self measureBlock:^{ for (int i = 0; i < 100; i++) { NSString *episodeId = [NSString stringWithFormat:@"perf_ep_%d", i]; [self.storage saveProgressWithEpisodeId:episodeId dramaId:@"perf_drama" position:i * 1000 duration:120000 isCompleted:NO synced:NO completion:nil]; } }]; } - (void)testPerformanceQueryProgresses { // 准备数据 for (int i = 0; i < 100; i++) { NSString *episodeId = [NSString stringWithFormat:@"query_ep_%d", i]; [self.storage saveProgressWithEpisodeId:episodeId dramaId:@"query_drama" position:i * 1000 duration:120000 isCompleted:NO synced:NO completion:nil]; } // 测试查询性能 [self measureBlock:^{ NSArray *progresses = [self.storage getProgressByDramaId:@"query_drama"]; XCTAssertGreaterThan(progresses.count, 0); }]; } @end