3 declare(strict_types=1);
5 namespace Tests\Commands;
7 use BookStack\Console\Commands\RefreshAvatarCommand;
8 use BookStack\Uploads\UserAvatars;
9 use BookStack\Users\Models\User;
10 use GuzzleHttp\Psr7\Response;
11 use Illuminate\Database\Eloquent\Collection;
12 use Symfony\Component\Console\Command\Command;
15 final class RefreshAvatarCommandTest extends TestCase
17 public function test_command_requires_email_or_id_option()
19 $this->artisan(RefreshAvatarCommand::class)
20 ->expectsOutput('Either a --id=<number> or --email=<email> option must be provided.')
21 ->assertExitCode(Command::FAILURE);
24 public function test_command_runs_with_provided_email()
26 $requests = $this->mockHttpClient([new Response(200, ['Content-Type' => 'image/png'], $this->files->pngImageData())]);
27 config()->set(['services.disable_services' => false]);
29 /** @var User $user */
30 $user = User::query()->first();
32 /** @var UserAvatars $avatar */
33 $avatar = app()->make(UserAvatars::class);
34 $avatar->destroyAllForUser($user);
36 $this->assertFalse($user->avatar()->exists());
37 $this->artisan(RefreshAvatarCommand::class, ['--email' => $user->email])
38 ->expectsOutputToContain("- ID: {$user->id}")
39 ->expectsQuestion('Are you sure you want to proceed?', true)
40 ->expectsOutput('User avatar has been updated.')
41 ->assertExitCode(Command::SUCCESS);
43 $expectedUri = 'https://www.gravatar.com/avatar/' . md5(strtolower($user->email)) . '?s=500&d=identicon';
44 $this->assertEquals($expectedUri, $requests->latestRequest()->getUri());
47 $this->assertTrue($user->avatar()->exists());
50 public function test_command_runs_with_provided_id()
52 $requests = $this->mockHttpClient([new Response(200, ['Content-Type' => 'image/png'], $this->files->pngImageData())]);
53 config()->set(['services.disable_services' => false]);
55 /** @var User $user */
56 $user = User::query()->first();
58 /** @var UserAvatars $avatar */
59 $avatar = app()->make(UserAvatars::class);
60 $avatar->destroyAllForUser($user);
62 $this->assertFalse($user->avatar()->exists());
63 $this->artisan(RefreshAvatarCommand::class, ['--id' => $user->id])
64 ->expectsOutputToContain("- ID: {$user->id}")
65 ->expectsQuestion('Are you sure you want to proceed?', true)
66 ->expectsOutput('User avatar has been updated.')
67 ->assertExitCode(Command::SUCCESS);
69 $expectedUri = 'https://www.gravatar.com/avatar/' . md5(strtolower($user->email)) . '?s=500&d=identicon';
70 $this->assertEquals($expectedUri, $requests->latestRequest()->getUri());
73 $this->assertTrue($user->avatar()->exists());
76 public function test_command_runs_with_provided_id_error_upstream()
78 $requests = $this->mockHttpClient([new Response(404)]);
79 config()->set(['services.disable_services' => false]);
81 /** @var User $user */
82 $user = User::query()->first();
83 /** @var UserAvatars $avatar */
84 $avatar = app()->make(UserAvatars::class);
85 $avatar->assignToUserFromExistingData($user, $this->files->pngImageData(), 'png');
87 $oldId = $user->avatar->id ?? 0;
89 $this->artisan(RefreshAvatarCommand::class, ['--id' => $user->id])
90 ->expectsOutputToContain("- ID: {$user->id}")
91 ->expectsQuestion('Are you sure you want to proceed?', true)
92 ->expectsOutput('Could not update avatar please review logs.')
93 ->assertExitCode(Command::FAILURE);
95 $this->assertEquals(1, $requests->requestCount());
98 $newId = $user->avatar->id ?? $oldId;
99 $this->assertEquals($oldId, $newId);
102 public function test_saying_no_to_confirmation_does_not_refresh_avatar()
104 /** @var User $user */
105 $user = User::query()->first();
107 $this->assertFalse($user->avatar()->exists());
108 $this->artisan(RefreshAvatarCommand::class, ['--id' => $user->id])
109 ->expectsQuestion('Are you sure you want to proceed?', false)
110 ->assertExitCode(Command::FAILURE);
111 $this->assertFalse($user->avatar()->exists());
114 public function test_giving_non_existing_user_shows_error_message()
118 ->assertExitCode(Command::FAILURE);
121 public function test_command_runs_all_users_without_avatars_dry_run()
123 $users = User::query()->where('image_id', '=', 0)->get();
125 $this->artisan(RefreshAvatarCommand::class, ['--users-without-avatars' => true])
126 ->expectsOutput(count($users) . ' user(s) found without avatars.')
127 ->expectsOutput("ID {$users[0]->id} - ")
128 ->expectsOutput('Not updated')
129 ->expectsOutput('Dry run, no avatars have been updated')
130 ->assertExitCode(Command::SUCCESS);
133 public function test_command_runs_all_users_without_avatars_non_to_update()
135 config()->set(['services.disable_services' => false]);
137 /** @var UserAvatars $avatar */
138 $avatar = app()->make(UserAvatars::class);
140 /** @var Collection|User[] $users */
141 $users = User::query()->get();
143 foreach ($users as $user) {
144 $avatar->fetchAndAssignToUser($user);
145 $responses[] = new Response(200, ['Content-Type' => 'image/png'], $this->files->pngImageData());
147 $requests = $this->mockHttpClient($responses);
149 $this->artisan(RefreshAvatarCommand::class, ['--users-without-avatars' => true, '-f' => true])
150 ->expectsOutput('0 user(s) found without avatars.')
151 ->expectsQuestion('Are you sure you want to refresh avatars of users that do not have one?', true)
152 ->assertExitCode(Command::SUCCESS);
154 $userWithAvatars = User::query()->where('image_id', '==', 0)->count();
155 $this->assertEquals(0, $userWithAvatars);
156 $this->assertEquals(0, $requests->requestCount());
159 public function test_command_runs_all_users_without_avatars()
161 config()->set(['services.disable_services' => false]);
163 /** @var UserAvatars $avatar */
164 $avatar = app()->make(UserAvatars::class);
166 /** @var Collection|User[] $users */
167 $users = User::query()->get();
168 foreach ($users as $user) {
169 $avatar->destroyAllForUser($user);
172 /** @var Collection|User[] $users */
173 $users = User::query()->where('image_id', '=', 0)->get();
175 $pendingCommand = $this->artisan(RefreshAvatarCommand::class, ['--users-without-avatars' => true, '-f' => true]);
177 ->expectsOutput($users->count() . ' user(s) found without avatars.')
178 ->expectsQuestion('Are you sure you want to refresh avatars of users that do not have one?', true);
181 foreach ($users as $user) {
182 $pendingCommand->expectsOutput("ID {$user->id} - ");
183 $pendingCommand->expectsOutput('Updated');
184 $responses[] = new Response(200, ['Content-Type' => 'image/png'], $this->files->pngImageData());
186 $requests = $this->mockHttpClient($responses);
188 $pendingCommand->assertExitCode(Command::SUCCESS);
189 $pendingCommand->run();
191 $userWithAvatars = User::query()->where('image_id', '!=', 0)->count();
192 $this->assertEquals($users->count(), $userWithAvatars);
193 $this->assertEquals($users->count(), $requests->requestCount());
196 public function test_saying_no_to_confirmation_all_users_without_avatars()
198 $requests = $this->mockHttpClient([new Response(200, ['Content-Type' => 'image/png'], $this->files->pngImageData())]);
199 config()->set(['services.disable_services' => false]);
201 /** @var UserAvatars $avatar */
202 $avatar = app()->make(UserAvatars::class);
204 /** @var Collection|User[] $users */
205 $users = User::query()->get();
206 foreach ($users as $user) {
207 $avatar->destroyAllForUser($user);
210 $this->artisan(RefreshAvatarCommand::class, ['--users-without-avatars' => true, '-f' => true])
211 ->expectsQuestion('Are you sure you want to refresh avatars of users that do not have one?', false)
212 ->assertExitCode(Command::SUCCESS);
214 $userWithAvatars = User::query()->where('image_id', '=', 0)->count();
215 $this->assertEquals($users->count(), $userWithAvatars);
216 $this->assertEquals(0, $requests->requestCount());
219 public function test_command_runs_all_users_dry_run()
221 $users = User::query()->where('image_id', '=', 0)->get();
223 $this->artisan(RefreshAvatarCommand::class, ['--all' => true])
224 ->expectsOutput(count($users) . ' user(s) found.')
225 ->expectsOutput("ID {$users[0]->id} - ")
226 ->expectsOutput('Not updated')
227 ->expectsOutput('Dry run, no avatars have been updated')
228 ->assertExitCode(Command::SUCCESS);
231 public function test_command_runs_update_all_users_avatar()
233 config()->set(['services.disable_services' => false]);
235 /** @var Collection|User[] $users */
236 $users = User::query()->get();
238 $pendingCommand = $this->artisan(RefreshAvatarCommand::class, ['--all' => true, '-f' => true]);
240 ->expectsOutput($users->count() . ' user(s) found.')
241 ->expectsQuestion('Are you sure you want to refresh avatars for ALL USERS?', true);
244 foreach ($users as $user) {
245 $pendingCommand->expectsOutput("ID {$user->id} - ");
246 $pendingCommand->expectsOutput('Updated');
247 $responses[] = new Response(200, ['Content-Type' => 'image/png'], $this->files->pngImageData());
249 $requests = $this->mockHttpClient($responses);
251 $pendingCommand->assertExitCode(Command::SUCCESS);
252 $pendingCommand->run();
254 $userWithAvatars = User::query()->where('image_id', '!=', 0)->count();
255 $this->assertEquals($users->count(), $userWithAvatars);
256 $this->assertEquals($users->count(), $requests->requestCount());
259 public function test_command_runs_update_all_users_avatar_errors()
261 config()->set(['services.disable_services' => false]);
263 /** @var Collection|User[] $users */
264 $users = User::query()->get();
266 $pendingCommand = $this->artisan(RefreshAvatarCommand::class, ['--all' => true, '-f' => true]);
268 ->expectsOutput($users->count() . ' user(s) found.')
269 ->expectsQuestion('Are you sure you want to refresh avatars for ALL USERS?', true);
272 foreach ($users as $key => $user) {
273 $pendingCommand->expectsOutput("ID {$user->id} - ");
276 $pendingCommand->expectsOutput('Not updated');
277 $responses[] = new Response(404);
281 $pendingCommand->expectsOutput('Updated');
282 $responses[] = new Response(200, ['Content-Type' => 'image/png'], $this->files->pngImageData());
285 $requests = $this->mockHttpClient($responses);
287 $pendingCommand->assertExitCode(Command::FAILURE);
288 $pendingCommand->run();
290 $userWithAvatars = User::query()->where('image_id', '!=', 0)->count();
291 $this->assertEquals($users->count() - 1, $userWithAvatars);
292 $this->assertEquals($users->count(), $requests->requestCount());
295 public function test_saying_no_to_confirmation_update_all_users_avatar()
297 $requests = $this->mockHttpClient([new Response(200, ['Content-Type' => 'image/png'], $this->files->pngImageData())]);
298 config()->set(['services.disable_services' => false]);
300 /** @var UserAvatars $avatar */
301 $avatar = app()->make(UserAvatars::class);
303 /** @var Collection|User[] $users */
304 $users = User::query()->get();
305 foreach ($users as $user) {
306 $avatar->destroyAllForUser($user);
309 $this->artisan(RefreshAvatarCommand::class, ['--all' => true, '-f' => true])
310 ->expectsQuestion('Are you sure you want to refresh avatars for ALL USERS?', false)
311 ->assertExitCode(Command::SUCCESS);
313 $userWithAvatars = User::query()->where('image_id', '=', 0)->count();
314 $this->assertEquals($users->count(), $userWithAvatars);
315 $this->assertEquals(0, $requests->requestCount());