create([ 'data_strategy' => 'webhook', 'data_payload' => ['old' => 'data'], ]); // Make request to update plugin data $response = $this->postJson("/api/custom_plugins/{$plugin->uuid}", [ 'merge_variables' => ['new' => 'data'], ]); // Assert response $response->assertOk() ->assertJson(['message' => 'Data updated successfully']); // Assert plugin was updated $this->assertDatabaseHas('plugins', [ 'id' => $plugin->id, 'data_payload' => json_encode(['new' => 'data']), ]); }); test('webhook returns stored merge variables for webhook strategy', function (): void { $plugin = Plugin::factory()->create([ 'data_strategy' => 'webhook', 'data_payload' => [ 'text' => 'Latest payload', 'metrics' => ['temperature' => 42], ], ]); $response = $this->getJson("/api/custom_plugins/{$plugin->uuid}"); $response->assertOk() ->assertJson([ 'merge_variables' => [ 'text' => 'Latest payload', 'metrics' => ['temperature' => 42], ], ]); }); test('webhook returns 400 for non-webhook strategy plugins', function (): void { // Create a plugin with non-webhook strategy $plugin = Plugin::factory()->create([ 'data_strategy' => 'polling', 'data_payload' => ['old' => 'data'], ]); // Make request to update plugin data $response = $this->postJson("/api/custom_plugins/{$plugin->uuid}", [ 'merge_variables' => ['new' => 'data'], ]); // Assert response $response->assertStatus(400) ->assertJson(['error' => 'Plugin does not use webhook strategy']); }); test('webhook deep_merge updates nested payload keys without replacing siblings', function (): void { $plugin = Plugin::factory()->create([ 'data_strategy' => 'webhook', 'data_payload' => [ 'sensor' => [ 'temperature' => 40, 'humidity' => 55, ], 'status' => 'ok', ], ]); $response = $this->postJson("/api/custom_plugins/{$plugin->uuid}", [ 'merge_variables' => [ 'sensor' => ['temperature' => 42], ], 'merge_strategy' => 'deep_merge', ]); $response->assertOk() ->assertJson(['message' => 'Data updated successfully']); expect($plugin->fresh()->data_payload)->toBe([ 'sensor' => [ 'temperature' => 42, 'humidity' => 55, ], 'status' => 'ok', ]); }); test('webhook stream appends top level arrays and preserves other keys', function (): void { $plugin = Plugin::factory()->create([ 'data_strategy' => 'webhook', 'data_payload' => [ 'temperatures' => [38, 39], 'status' => 'ok', ], ]); $response = $this->postJson("/api/custom_plugins/{$plugin->uuid}", [ 'merge_variables' => [ 'temperatures' => [40, 42], 'status' => 'alert', ], 'merge_strategy' => 'stream', ]); $response->assertOk(); expect($plugin->fresh()->data_payload)->toBe([ 'temperatures' => [38, 39, 40, 42], 'status' => 'alert', ]); }); test('webhook stream_limit trims older streamed items', function (): void { $plugin = Plugin::factory()->create([ 'data_strategy' => 'webhook', 'data_payload' => [ 'temperatures' => [36, 37, 38], ], ]); $response = $this->postJson("/api/custom_plugins/{$plugin->uuid}", [ 'merge_variables' => [ 'temperatures' => [39, 40], ], 'merge_strategy' => 'stream', 'stream_limit' => 4, ]); $response->assertOk(); expect($plugin->fresh()->data_payload)->toBe([ 'temperatures' => [37, 38, 39, 40], ]); }); test('webhook returns 400 when merge_variables is missing', function (): void { // Create a plugin with webhook strategy $plugin = Plugin::factory()->create([ 'data_strategy' => 'webhook', 'data_payload' => ['old' => 'data'], ]); // Make request without merge_variables $response = $this->postJson("/api/custom_plugins/{$plugin->uuid}", []); // Assert response $response->assertStatus(400) ->assertJson(['error' => 'Request must contain merge_variables key']); }); test('webhook returns 400 for invalid merge_strategy', function (): void { $plugin = Plugin::factory()->create([ 'data_strategy' => 'webhook', ]); $response = $this->postJson("/api/custom_plugins/{$plugin->uuid}", [ 'merge_variables' => ['new' => 'data'], 'merge_strategy' => 'append_forever', ]); $response->assertStatus(400) ->assertJson(['error' => 'merge_strategy must be one of: deep_merge, stream']); }); test('webhook returns 400 for invalid stream_limit', function (): void { $plugin = Plugin::factory()->create([ 'data_strategy' => 'webhook', ]); $response = $this->postJson("/api/custom_plugins/{$plugin->uuid}", [ 'merge_variables' => ['temperatures' => [42]], 'merge_strategy' => 'stream', 'stream_limit' => 0, ]); $response->assertStatus(400) ->assertJson(['error' => 'stream_limit must be a positive integer']); }); test('webhook returns 404 for non-existent plugin', function (): void { // Make request with non-existent plugin UUID $response = $this->postJson('/api/custom_plugins/'.Str::uuid(), [ 'merge_variables' => ['new' => 'data'], ]); // Assert response $response->assertNotFound(); }); test('webhook rejects merge_variables that exceed the wire size limit', function (): void { // Tiny limit so a small payload can exceed the budget (max_size - 512 bytes). config(['livewire.payload.max_size' => 768]); $plugin = Plugin::factory()->create([ 'data_strategy' => 'webhook', 'data_payload' => ['old' => 'data'], ]); $oversized = ['blob' => str_repeat('A', 1024)]; $response = $this->postJson("/api/custom_plugins/{$plugin->uuid}", [ 'merge_variables' => $oversized, ]); $response->assertStatus(413) ->assertJson(Plugin::oversizedDataPayloadErrorPayload()); expect($plugin->fresh()->data_payload)->toBe(['old' => 'data']); }); test('webhook rejects merged payloads that exceed the wire size limit', function (): void { config(['livewire.payload.max_size' => 768]); $plugin = Plugin::factory()->create([ 'data_strategy' => 'webhook', 'data_payload' => ['temperatures' => [str_repeat('A', 240)]], ]); $response = $this->postJson("/api/custom_plugins/{$plugin->uuid}", [ 'merge_variables' => [ 'temperatures' => [str_repeat('B', 240)], ], 'merge_strategy' => 'stream', ]); $response->assertStatus(413) ->assertJson(Plugin::oversizedDataPayloadErrorPayload()); expect($plugin->fresh()->data_payload)->toBe([ 'temperatures' => [str_repeat('A', 240)], ]); });