Maintain local ⭤ remote in sync with automatic AT Protocol parity for Laravel (alpha & unstable)
1<?php
2
3namespace SocialDept\AtpParity\Tests\Unit\Import;
4
5use Illuminate\Support\Facades\Event;
6use Mockery;
7use SocialDept\AtpParity\Events\ImportCompleted;
8use SocialDept\AtpParity\Events\ImportFailed;
9use SocialDept\AtpParity\Events\ImportProgress;
10use SocialDept\AtpParity\Events\ImportStarted;
11use SocialDept\AtpParity\Import\ImportService;
12use SocialDept\AtpParity\Import\ImportState;
13use SocialDept\AtpParity\MapperRegistry;
14use SocialDept\AtpParity\Tests\Fixtures\TestMapper;
15use SocialDept\AtpParity\Tests\Fixtures\TestModel;
16use SocialDept\AtpParity\Tests\TestCase;
17use SocialDept\AtpResolver\Facades\Resolver;
18
19class ImportServiceTest extends TestCase
20{
21 private ImportService $service;
22
23 private MapperRegistry $registry;
24
25 protected function setUp(): void
26 {
27 parent::setUp();
28
29 $this->registry = new MapperRegistry();
30 $this->registry->register(new TestMapper());
31
32 $this->service = new ImportService($this->registry);
33
34 Event::fake();
35 }
36
37 public function test_import_user_collection_returns_failed_when_no_mapper(): void
38 {
39 $this->app->forgetInstance(MapperRegistry::class);
40 $emptyRegistry = new MapperRegistry();
41 $service = new ImportService($emptyRegistry);
42
43 $result = $service->importUserCollection('did:plc:test', 'unknown.collection');
44
45 $this->assertTrue($result->isFailed());
46 $this->assertStringContainsString('No mapper registered', $result->error);
47 }
48
49 public function test_import_user_collection_fails_when_pds_not_resolved(): void
50 {
51 Resolver::shouldReceive('resolvePds')
52 ->with('did:plc:test')
53 ->andReturnNull();
54
55 $result = $this->service->importUserCollection('did:plc:test', 'app.test.record');
56
57 $this->assertTrue($result->isFailed());
58 $this->assertStringContainsString('Could not resolve PDS', $result->error);
59 Event::assertDispatched(ImportFailed::class);
60 }
61
62 /**
63 * @group integration
64 */
65 public function test_import_user_collection_imports_records(): void
66 {
67 $this->markTestSkipped('Requires integration test with real or mock ATP client - AtpClient has typed properties that prevent mocking');
68 }
69
70 /**
71 * @group integration
72 */
73 public function test_import_dispatches_events(): void
74 {
75 $this->markTestSkipped('Requires integration test with real or mock ATP client - AtpClient has typed properties that prevent mocking');
76 }
77
78 /**
79 * @group integration
80 */
81 public function test_import_calls_progress_callback(): void
82 {
83 $this->markTestSkipped('Requires integration test with real or mock ATP client - AtpClient has typed properties that prevent mocking');
84 }
85
86 /**
87 * @group integration
88 */
89 public function test_import_user_imports_multiple_collections(): void
90 {
91 $this->markTestSkipped('Requires integration test with real or mock ATP client - AtpClient has typed properties that prevent mocking');
92 }
93
94 public function test_get_status_returns_import_state(): void
95 {
96 ImportState::create([
97 'did' => 'did:plc:test',
98 'collection' => 'app.test.record',
99 'status' => 'completed',
100 'records_synced' => 50,
101 ]);
102
103 $state = $this->service->getStatus('did:plc:test', 'app.test.record');
104
105 $this->assertNotNull($state);
106 $this->assertSame('completed', $state->status);
107 $this->assertSame(50, $state->records_synced);
108 }
109
110 public function test_get_status_returns_null_when_not_found(): void
111 {
112 $state = $this->service->getStatus('did:plc:unknown', 'unknown');
113
114 $this->assertNull($state);
115 }
116
117 public function test_is_imported_returns_true_when_completed(): void
118 {
119 ImportState::create([
120 'did' => 'did:plc:test',
121 'collection' => 'app.test.record',
122 'status' => 'completed',
123 ]);
124
125 $this->assertTrue($this->service->isImported('did:plc:test', 'app.test.record'));
126 }
127
128 public function test_is_imported_returns_false_when_not_started(): void
129 {
130 $this->assertFalse($this->service->isImported('did:plc:test', 'app.test.record'));
131 }
132
133 public function test_reset_deletes_import_state(): void
134 {
135 ImportState::create([
136 'did' => 'did:plc:test',
137 'collection' => 'app.test.record',
138 'status' => 'completed',
139 ]);
140
141 $this->service->reset('did:plc:test', 'app.test.record');
142
143 $this->assertNull($this->service->getStatus('did:plc:test', 'app.test.record'));
144 }
145
146 public function test_import_skips_already_completed(): void
147 {
148 ImportState::create([
149 'did' => 'did:plc:test',
150 'collection' => 'app.test.record',
151 'status' => 'completed',
152 'records_synced' => 100,
153 ]);
154
155 // No mocking needed - should return cached result
156 $result = $this->service->importUserCollection('did:plc:test', 'app.test.record');
157
158 $this->assertTrue($result->completed);
159 $this->assertSame(100, $result->recordsSynced);
160 }
161
162 /**
163 * @group integration
164 */
165 public function test_import_handles_record_failures_gracefully(): void
166 {
167 $this->markTestSkipped('Requires integration test with real or mock ATP client - AtpClient has typed properties that prevent mocking');
168 }
169}