diff --git a/app/Console/Commands/AssignSortSetCommand.php b/app/Console/Commands/AssignSortSetCommand.php
index aca046bae..484f69952 100644
--- a/app/Console/Commands/AssignSortSetCommand.php
+++ b/app/Console/Commands/AssignSortSetCommand.php
@@ -50,7 +50,7 @@ class AssignSortSetCommand extends Command
             }
             $query = Book::query()->where('sort_set_id', $sortId);
         } else {
-            $this->error("Either the --all-books or --books-without-sort option must be provided!");
+            $this->error("No option provided to specify target. Run with the -h option to see all available options.");
             return 1;
         }
 
@@ -79,7 +79,7 @@ class AssignSortSetCommand extends Command
             $processed = $max;
         });
 
-        $this->info("Sort applied to {$processed} books!");
+        $this->info("Sort applied to {$processed} book(s)!");
 
         return 0;
     }
diff --git a/app/Sorting/SortSet.php b/app/Sorting/SortSet.php
index 8cdee1df4..cc8879f96 100644
--- a/app/Sorting/SortSet.php
+++ b/app/Sorting/SortSet.php
@@ -6,6 +6,7 @@ use BookStack\Activity\Models\Loggable;
 use BookStack\Entities\Models\Book;
 use Carbon\Carbon;
 use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\Relations\HasMany;
 
@@ -18,6 +19,8 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
  */
 class SortSet extends Model implements Loggable
 {
+    use HasFactory;
+
     /**
      * @return SortSetOperation[]
      */
diff --git a/database/factories/Sorting/SortSetFactory.php b/database/factories/Sorting/SortSetFactory.php
new file mode 100644
index 000000000..36e0a6976
--- /dev/null
+++ b/database/factories/Sorting/SortSetFactory.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Database\Factories\Sorting;
+
+use BookStack\Sorting\SortSet;
+use BookStack\Sorting\SortSetOperation;
+use Illuminate\Database\Eloquent\Factories\Factory;
+
+class SortSetFactory extends Factory
+{
+    /**
+     * The name of the factory's corresponding model.
+     *
+     * @var string
+     */
+    protected $model = SortSet::class;
+
+    /**
+     * Define the model's default state.
+     */
+    public function definition(): array
+    {
+        $cases = SortSetOperation::cases();
+        $op = $cases[array_rand($cases)];
+        return [
+            'name' => $op->name . ' Sort',
+            'sequence' => $op->value,
+        ];
+    }
+}
diff --git a/tests/Commands/AssignSortSetCommandTest.php b/tests/Commands/AssignSortSetCommandTest.php
new file mode 100644
index 000000000..01cb5caa5
--- /dev/null
+++ b/tests/Commands/AssignSortSetCommandTest.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace Commands;
+
+use BookStack\Entities\Models\Book;
+use BookStack\Sorting\SortSet;
+use Tests\TestCase;
+
+class AssignSortSetCommandTest extends TestCase
+{
+    public function test_no_given_sort_set_lists_options()
+    {
+        $sortSets = SortSet::factory()->createMany(10);
+
+        $commandRun = $this->artisan('bookstack:assign-sort-set')
+            ->expectsOutputToContain('Sort set ID required!')
+            ->assertExitCode(1);
+
+        foreach ($sortSets as $sortSet) {
+            $commandRun->expectsOutputToContain("{$sortSet->id}: {$sortSet->name}");
+        }
+    }
+
+    public function test_run_without_options_advises_help()
+    {
+        $this->artisan("bookstack:assign-sort-set 100")
+            ->expectsOutput("No option provided to specify target. Run with the -h option to see all available options.")
+            ->assertExitCode(1);
+    }
+
+    public function test_run_without_valid_sort_advises_help()
+    {
+        $this->artisan("bookstack:assign-sort-set 100342 --all-books")
+            ->expectsOutput("Sort set of provided id 100342 not found!")
+            ->assertExitCode(1);
+    }
+
+    public function test_confirmation_required()
+    {
+        $sortSet = SortSet::factory()->create();
+
+        $this->artisan("bookstack:assign-sort-set {$sortSet->id} --all-books")
+            ->expectsConfirmation('Are you sure you want to continue?', 'no')
+            ->assertExitCode(1);
+
+        $booksWithSort = Book::query()->whereNotNull('sort_set_id')->count();
+        $this->assertEquals(0, $booksWithSort);
+    }
+
+    public function test_assign_to_all_books()
+    {
+        $sortSet = SortSet::factory()->create();
+        $booksWithoutSort = Book::query()->whereNull('sort_set_id')->count();
+        $this->assertGreaterThan(0, $booksWithoutSort);
+
+        $this->artisan("bookstack:assign-sort-set {$sortSet->id} --all-books")
+            ->expectsOutputToContain("This will apply sort set [{$sortSet->id}: {$sortSet->name}] to {$booksWithoutSort} book(s)")
+            ->expectsConfirmation('Are you sure you want to continue?', 'yes')
+            ->expectsOutputToContain("Sort applied to {$booksWithoutSort} book(s)")
+            ->assertExitCode(0);
+
+        $booksWithoutSort = Book::query()->whereNull('sort_set_id')->count();
+        $this->assertEquals(0, $booksWithoutSort);
+    }
+
+    public function test_assign_to_all_books_without_sort()
+    {
+        $totalBooks = Book::query()->count();
+        $book = $this->entities->book();
+        $sortSetA = SortSet::factory()->create();
+        $sortSetB = SortSet::factory()->create();
+        $book->sort_set_id = $sortSetA->id;
+        $book->save();
+
+        $booksWithoutSort = Book::query()->whereNull('sort_set_id')->count();
+        $this->assertEquals($totalBooks, $booksWithoutSort + 1);
+
+        $this->artisan("bookstack:assign-sort-set {$sortSetB->id} --books-without-sort")
+            ->expectsConfirmation('Are you sure you want to continue?', 'yes')
+            ->expectsOutputToContain("Sort applied to {$booksWithoutSort} book(s)")
+            ->assertExitCode(0);
+
+        $booksWithoutSort = Book::query()->whereNull('sort_set_id')->count();
+        $this->assertEquals(0, $booksWithoutSort);
+        $this->assertEquals($totalBooks, $sortSetB->books()->count() + 1);
+    }
+
+    public function test_assign_to_all_books_with_sort()
+    {
+        $book = $this->entities->book();
+        $sortSetA = SortSet::factory()->create();
+        $sortSetB = SortSet::factory()->create();
+        $book->sort_set_id = $sortSetA->id;
+        $book->save();
+
+        $this->artisan("bookstack:assign-sort-set {$sortSetB->id} --books-with-sort={$sortSetA->id}")
+            ->expectsConfirmation('Are you sure you want to continue?', 'yes')
+            ->expectsOutputToContain("Sort applied to 1 book(s)")
+            ->assertExitCode(0);
+
+        $book->refresh();
+        $this->assertEquals($sortSetB->id, $book->sort_set_id);
+        $this->assertEquals(1, $sortSetB->books()->count());
+    }
+
+    public function test_assign_to_all_books_with_sort_id_is_validated()
+    {
+        $this->artisan("bookstack:assign-sort-set 50 --books-with-sort=beans")
+            ->expectsOutputToContain("Provided --books-with-sort option value is invalid")
+            ->assertExitCode(1);
+    }
+}