trypost/tests/Feature/Automation/VariablesTest.php
Paulo Castellano 9a692b4608 Enhance automation functionality: Introduce workflow variables and improve node validation
- Added support for workflow variables in automations, allowing users to define reusable values.
- Implemented validation for Generate nodes to ensure intended image counts align with selected accounts.
- Updated automation models and requests to handle new variables, including encryption for sensitive data.
- Enhanced UI to display variables and their management within the automation editor.
- Improved error handling for webhook and HTTP nodes to prevent requests to invalid URLs.
- Refactored various components for better context resolution during automation runs.
2026-06-11 15:47:29 -03:00

161 lines
6 KiB
PHP

<?php
declare(strict_types=1);
use App\Actions\Automation\Node\RunConditionNode;
use App\Actions\Automation\Node\RunFetchRssNode;
use App\Actions\Automation\Node\RunWebhookNode;
use App\Enums\UserWorkspace\Role;
use App\Http\Resources\AutomationResource;
use App\Models\Automation;
use App\Models\AutomationRun;
use App\Models\User;
use App\Models\Workspace;
use App\Services\Automation\ExpressionResolver;
use Illuminate\Support\Facades\Http;
beforeEach(function () {
$this->workspace = Workspace::factory()->create();
$this->user = User::factory()->create(['current_workspace_id' => $this->workspace->id]);
$this->workspace->members()->attach($this->user->id, ['role' => Role::Admin->value]);
$this->user->refresh();
});
it('stores variable values encrypted and decrypts them for runtime', function () {
$automation = Automation::factory()->for($this->workspace)->create();
$this->actingAs($this->user)
->put(route('app.automations.update', $automation->id), [
'variables' => [
['key' => 'API_KEY', 'value' => 'super-secret'],
['key' => 'BASE_URL', 'value' => 'https://api.example.com'],
],
])
->assertRedirect();
$automation->refresh();
expect($automation->variables[0]['value'])->not->toBe('super-secret');
expect($automation->resolvedVariables())->toBe([
'API_KEY' => 'super-secret',
'BASE_URL' => 'https://api.example.com',
]);
});
it('masks variable values in the resource', function () {
$automation = Automation::factory()->for($this->workspace)->create([
'variables' => [['key' => 'API_KEY', 'value' => 'super-secret']],
]);
$resource = (new AutomationResource($automation->fresh()))->toArray(request());
expect($resource['variables'][0]['key'])->toBe('API_KEY');
expect($resource['variables'][0]['value'])->toBe(Automation::SENSITIVE_PLACEHOLDER);
});
it('keeps the stored value when the masked placeholder is re-saved', function () {
$automation = Automation::factory()->for($this->workspace)->create([
'variables' => [['key' => 'API_KEY', 'value' => 'secret-1']],
]);
$this->actingAs($this->user)
->put(route('app.automations.update', $automation->id), [
'variables' => [['key' => 'API_KEY', 'value' => Automation::SENSITIVE_PLACEHOLDER]],
])
->assertRedirect();
expect($automation->fresh()->resolvedVariables())->toBe(['API_KEY' => 'secret-1']);
});
it('resolves {{ variables.X }} in a node template', function () {
$resolved = app(ExpressionResolver::class)->resolve(
'Bearer {{ variables.API_KEY }}',
['variables' => ['API_KEY' => 'abc123']],
);
expect($resolved)->toBe('Bearer abc123');
});
it('merges variables into resolverContext but never into the persisted run context', function () {
$automation = Automation::factory()->for($this->workspace)->create([
'variables' => [['key' => 'API_KEY', 'value' => 'secret-xyz']],
]);
$run = AutomationRun::factory()->for($automation)->create(['context' => ['trigger' => ['x' => 1]]]);
expect($run->resolverContext()['variables'])->toBe(['API_KEY' => 'secret-xyz']);
expect($run->context)->not->toHaveKey('variables');
expect($run->fresh()->context)->not->toHaveKey('variables');
});
it('resolves a variable in a webhook payload without persisting it in the run context', function () {
Http::fake(['1.1.1.1/*' => Http::response(['ok' => true], 200)]);
$automation = Automation::factory()->for($this->workspace)->create([
'variables' => [['key' => 'TOKEN', 'value' => 'abc123']],
]);
$run = AutomationRun::factory()->for($automation)->create(['context' => []]);
app(RunWebhookNode::class)($run, [
'url' => 'https://1.1.1.1/hook',
'method' => 'POST',
'payload_template' => '{"token":"{{ variables.TOKEN }}"}',
]);
Http::assertSent(fn ($request) => $request['token'] === 'abc123');
expect($run->context)->not->toHaveKey('variables');
});
it('resolves a variable in an RSS feed url', function () {
Http::fake(['1.1.1.1/*' => Http::response('<rss><channel></channel></rss>', 200)]);
$automation = Automation::factory()->for($this->workspace)->create([
'variables' => [['key' => 'HOST', 'value' => '1.1.1.1']],
]);
$run = AutomationRun::factory()->for($automation)->create(['context' => []]);
app(RunFetchRssNode::class)($run, ['feed_url' => 'https://{{ variables.HOST }}/feed.xml']);
Http::assertSent(fn ($request) => str_contains($request->url(), '1.1.1.1/feed.xml'));
});
it('resolves a variable in a condition comparison value', function () {
$automation = Automation::factory()->for($this->workspace)->create([
'variables' => [['key' => 'EXPECTED', 'value' => 'published']],
]);
$run = AutomationRun::factory()->for($automation)->create([
'context' => ['trigger' => ['post' => ['status' => 'published']]],
]);
$result = app(RunConditionNode::class)($run, [
'field' => '{{ trigger.post.status }}',
'operator' => 'equals',
'value' => '{{ variables.EXPECTED }}',
]);
expect($result->nextHandle)->toBe('yes');
});
it('rejects an invalid variable key', function () {
$automation = Automation::factory()->for($this->workspace)->create();
$this->actingAs($this->user)
->putJson(route('app.automations.update', $automation->id), [
'variables' => [['key' => 'bad key!', 'value' => 'x']],
])
->assertStatus(422)
->assertJsonValidationErrors(['variables.0.key']);
});
it('rejects duplicate variable keys', function () {
$automation = Automation::factory()->for($this->workspace)->create();
$this->actingAs($this->user)
->putJson(route('app.automations.update', $automation->id), [
'variables' => [
['key' => 'API_KEY', 'value' => 'a'],
['key' => 'API_KEY', 'value' => 'b'],
],
])
->assertStatus(422)
->assertJsonValidationErrors(['variables.0.key']);
});