Skip to content

Commit 8987717

Browse files
feat: add model, endpoint and tests for /api/v2/system/update
1 parent 9039f9e commit 8987717

5 files changed

Lines changed: 196 additions & 0 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace RESTAPI\Dispatchers;
4+
5+
use RESTAPI\Core\Command;
6+
use RESTAPI\Core\Dispatcher;
7+
8+
/**
9+
* Defines a Dispatcher for running pfSense package updates.
10+
*/
11+
class SystemUpdateDispatcher extends Dispatcher {
12+
# Only allow one upgrade process at a time
13+
public int $max_queue = 1;
14+
15+
/**
16+
* Updates package metadata and runs the pfSense upgrade process.
17+
*/
18+
protected function _process(mixed ...$arguments): void {
19+
# Accept either named or positional dry_run argument.
20+
$dry_run = false;
21+
if (array_key_exists('dry_run', $arguments)) {
22+
$dry_run = (bool) $arguments['dry_run'];
23+
} elseif (array_key_exists(0, $arguments)) {
24+
$dry_run = (bool) $arguments[0];
25+
}
26+
27+
$this->run_command('/usr/local/sbin/pkg-static update');
28+
29+
$upgrade_command = '/usr/local/sbin/pfSense-upgrade -y';
30+
if ($dry_run) {
31+
$upgrade_command .= ' -n';
32+
}
33+
34+
$this->run_command($upgrade_command);
35+
}
36+
37+
/**
38+
* Executes a shell command for this dispatcher process.
39+
*/
40+
protected function run_command(string $command): void {
41+
new Command($command);
42+
}
43+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace RESTAPI\Endpoints;
4+
5+
require_once 'RESTAPI/autoloader.inc';
6+
7+
use RESTAPI\Core\Endpoint;
8+
9+
/**
10+
* Defines an Endpoint for interacting with singular SystemUpdate Model objects at /api/v2/system/update.
11+
*/
12+
class SystemUpdateEndpoint extends Endpoint {
13+
public function __construct() {
14+
# Set Endpoint attributes
15+
$this->url = '/api/v2/system/update';
16+
$this->model_name = 'SystemUpdate';
17+
$this->request_method_options = ['POST'];
18+
$this->post_help_text = 'Initiates a pfSense update process.';
19+
20+
# Construct the parent Endpoint object
21+
parent::__construct();
22+
}
23+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace RESTAPI\Models;
4+
5+
require_once 'RESTAPI/autoloader.inc';
6+
7+
use RESTAPI\Core\Model;
8+
use RESTAPI\Dispatchers\SystemUpdateDispatcher;
9+
use RESTAPI\Fields\BooleanField;
10+
11+
/**
12+
* Defines a Model that performs a pfSense update operation.
13+
*/
14+
class SystemUpdate extends Model {
15+
public BooleanField $dry_run;
16+
17+
public function __construct(mixed $id = null, mixed $parent_id = null, mixed $data = [], ...$options) {
18+
# Set model attributes
19+
$this->many = false;
20+
$this->always_apply = true;
21+
22+
# Set model fields
23+
$this->dry_run = new BooleanField(
24+
default: false,
25+
write_only: true,
26+
verbose_name: 'Dry Run',
27+
help_text: 'Run the pfSense update process in dry-run mode (-n) without applying changes.',
28+
);
29+
30+
parent::__construct($id, $parent_id, $data, ...$options);
31+
}
32+
33+
/**
34+
* This model only triggers apply behavior, so this method intentionally does nothing.
35+
*/
36+
public function _create(): void {
37+
# Intentionally empty.
38+
}
39+
40+
/**
41+
* Spawns a dispatcher process that performs package update + pfSense upgrade.
42+
*/
43+
public function apply(): void {
44+
(new SystemUpdateDispatcher(async: $this->async))->spawn_process(dry_run: $this->dry_run->value);
45+
}
46+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace RESTAPI\Tests;
4+
5+
use RESTAPI\Core\TestCase;
6+
use RESTAPI\Dispatchers\SystemUpdateDispatcher;
7+
8+
class TestSystemUpdateDispatcher extends SystemUpdateDispatcher {
9+
public array $commands = [];
10+
11+
protected function run_command(string $command): void {
12+
$this->commands[] = $command;
13+
}
14+
}
15+
16+
class APIDispatchersSystemUpdateDispatcherTestCase extends TestCase {
17+
/**
18+
* Ensure the dispatcher enforces max_queue of one process.
19+
*/
20+
public function test_max_queue_is_one(): void {
21+
$dispatcher = new SystemUpdateDispatcher();
22+
$this->assert_equals($dispatcher->max_queue, 1);
23+
}
24+
25+
/**
26+
* Ensure the dispatcher calls pkg-static update and standard upgrade by default.
27+
*/
28+
public function test_process_without_dry_run_uses_standard_upgrade_command(): void {
29+
$dispatcher = new TestSystemUpdateDispatcher();
30+
$dispatcher->process();
31+
32+
$this->assert_equals($dispatcher->commands, [
33+
'/usr/local/sbin/pkg-static update',
34+
'/usr/local/sbin/pfSense-upgrade -y',
35+
]);
36+
}
37+
38+
/**
39+
* Ensure named dry_run appends -n to pfSense-upgrade.
40+
*/
41+
public function test_process_with_named_dry_run_uses_noop_upgrade_command(): void {
42+
$dispatcher = new TestSystemUpdateDispatcher();
43+
$dispatcher->process(dry_run: true);
44+
45+
$this->assert_equals($dispatcher->commands, [
46+
'/usr/local/sbin/pkg-static update',
47+
'/usr/local/sbin/pfSense-upgrade -y -n',
48+
]);
49+
}
50+
51+
/**
52+
* Ensure positional dry_run argument also appends -n to pfSense-upgrade.
53+
*/
54+
public function test_process_with_positional_dry_run_uses_noop_upgrade_command(): void {
55+
$dispatcher = new TestSystemUpdateDispatcher();
56+
$dispatcher->process(true);
57+
58+
$this->assert_equals($dispatcher->commands, [
59+
'/usr/local/sbin/pkg-static update',
60+
'/usr/local/sbin/pfSense-upgrade -y -n',
61+
]);
62+
}
63+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace RESTAPI\Tests;
4+
5+
use RESTAPI\Core\TestCase;
6+
use RESTAPI\Models\SystemUpdate;
7+
8+
class APIModelsSystemUpdateTestCase extends TestCase {
9+
/**
10+
* Check that the SystemUpdate model accepts and validates dry_run inputs.
11+
*/
12+
public function test_validate_dry_run_field(): void {
13+
$this->assert_does_not_throw(
14+
callable: function () {
15+
$update = new SystemUpdate(data: ['dry_run' => true]);
16+
$update->validate();
17+
$this->assert_is_true($update->dry_run->value);
18+
},
19+
);
20+
}
21+
}

0 commit comments

Comments
 (0)