diff --git a/app/Services/Automation/ExpressionResolver.php b/app/Services/Automation/ExpressionResolver.php index 492ed53d..f86b1557 100644 --- a/app/Services/Automation/ExpressionResolver.php +++ b/app/Services/Automation/ExpressionResolver.php @@ -58,6 +58,8 @@ private function resolveVariable(string $path, array $context): string return (string) $value; } - return json_encode($value); + // json_encode returns false on malformed UTF-8 (plausible for scraped + // feed/HTTP payloads); the method must still return a string. + return json_encode($value, JSON_PARTIAL_OUTPUT_ON_ERROR) ?: ''; } } diff --git a/tests/Unit/Automation/ExpressionResolverTest.php b/tests/Unit/Automation/ExpressionResolverTest.php index cbdb2f4a..bed7c6b6 100644 --- a/tests/Unit/Automation/ExpressionResolverTest.php +++ b/tests/Unit/Automation/ExpressionResolverTest.php @@ -50,3 +50,17 @@ it('passes through templates with no variables', function () { expect($this->resolver->resolve('plain text', []))->toBe('plain text'); }); + +it('json-encodes array values', function () { + $context = ['fetched' => ['tags' => ['a', 'b']]]; + + expect($this->resolver->resolve('{{ fetched.tags }}', $context))->toBe('["a","b"]'); +}); + +it('does not throw when an array value contains malformed UTF-8', function () { + // Scraped feed/HTTP payloads can carry invalid UTF-8; json_encode returns + // false for it, but the resolver must still yield a string. + $context = ['fetched' => ['body' => ["bad\xB1utf8"]]]; + + expect($this->resolver->resolve('{{ fetched.body }}', $context))->toBeString(); +});