Testrepository
**Site relevance:** Spiralist.org
**Memory type:** plugin source memory
**Source path:** Spiralist/wp-content/plugins/spiralist-workspace/includes/Domain/TestRepository.php
**Size:** 6.7 KB
Summary
namespace SpiralistWorkspace\Domain;
Source Preview
This source file is short enough to preview directly on its source-memory page.
<?php
namespace SpiralistWorkspace\Domain;
use SpiralistWorkspace\Installer;
if (!defined('ABSPATH')) {
exit;
}
/**
* Stores reusable prompt test cases and their latest evaluation result.
*/
class TestRepository
{
/**
* Creates a prompt test case.
*/
public function create(int $prompt_id, array $data, int $user_id): array
{
global $wpdb;
$variables = (array) ($data['variables'] ?? []);
$strategy = sanitize_key((string) ($data['match_strategy'] ?? 'contains'));
if (!in_array($strategy, ['contains', 'exact', 'json'], true)) {
$strategy = 'contains';
}
$wpdb->insert(
Installer::table('prompt_tests'),
[
'prompt_post_id' => $prompt_id,
'created_by_user_id' => max(0, $user_id),
'input_text' => sanitize_textarea_field((string) ($data['input_text'] ?? '')),
'expected_output_text' => sanitize_textarea_field((string) ($data['expected_output'] ?? ($data['expected_output_text'] ?? ''))),
'variables_json' => wp_json_encode($this->sanitize_variables($variables), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
'match_strategy' => $strategy,
'last_run_id' => null,
'last_result_status' => null,
'last_result_json' => null,
'created_utc' => gmdate('Y-m-d H:i:s'),
'updated_utc' => gmdate('Y-m-d H:i:s'),
],
['%d', '%d', '%s', '%s', '%s', '%s', '%d', '%s', '%s', '%s', '%s']
);
return $this->get((int) $wpdb->insert_id);
}
/**
* Lists tests for a prompt.
*/
public function list_for_prompt(int $prompt_id, int $limit = 50): array
{
global $wpdb;
$sql = $wpdb->prepare(
'SELECT * FROM ' . Installer::table('prompt_tests') . ' WHERE prompt_post_id = %d ORDER BY created_utc DESC LIMIT %d',
$prompt_id,
max(1, $limit)
);
return $this->normalize_rows($wpdb->get_results($sql, ARRAY_A));
}
/**
* Lists recent prompt tests across the workspace.
*/
public function list_all(int $limit = 50): array
{
global $wpdb;
$sql = $wpdb->prepare(
'SELECT * FROM ' . Installer::table('prompt_tests') . ' ORDER BY updated_utc DESC, created_utc DESC LIMIT %d',
max(1, $limit)
);
return $this->normalize_rows($wpdb->get_results($sql, ARRAY_A));
}
/**
* Returns one test case.
*/
public function get(int $test_id): array
{
global $wpdb;
$sql = $wpdb->prepare(
'SELECT * FROM ' . Installer::table('prompt_tests') . ' WHERE id = %d LIMIT 1',
$test_id
);
$row = $wpdb->get_row($sql, ARRAY_A);
return is_array($row) ? $this->normalize_row($row) : [];
}
/**
* Updates a test with its latest evaluation result.
*/
public function update_result(int $test_id, int $run_id, array $result): void
{
global $wpdb;
$wpdb->update(
Installer::table('prompt_tests'),
[
'last_run_id' => $run_id,
'last_result_status' => sanitize_key((string) ($result['status'] ?? 'failed')),
'last_result_json' => wp_json_encode($result, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
'updated_utc' => gmdate('Y-m-d H:i:s'),
],
['id' => $test_id],
['%d', '%s', '%s', '%s'],
['%d']
);
}
/**
* Evaluates a model output against a test expectation.
*/
public function evaluate(string $output, string $expected, string $strategy): array
{
$strategy = sanitize_key($strategy);
$normalized_output = $this->normalize_text($output);
$normalized_expected = $this->normalize_text($expected);
$passed = false;
$details = '';
if ($strategy === 'exact') {
$passed = $normalized_output === $normalized_expected;
$details = $passed ? 'Output matched exactly.' : 'Output did not exactly match the expected text.';
} elseif ($strategy === 'json') {
$decoded_output = json_decode($output, true);
$decoded_expected = json_decode($expected, true);
$passed = is_array($decoded_output) && is_array($decoded_expected) && $decoded_output == $decoded_expected;
$details = $passed ? 'JSON structures matched.' : 'JSON structures did not match or could not be decoded.';
} else {
$passed = $normalized_expected !== '' && strpos($normalized_output, $normalized_expected) !== false;
$details = $passed ? 'Expected text was found in the output.' : 'Expected text was not found in the output.';
$strategy = 'contains';
}
return [
'status' => $passed ? 'passed' : 'failed',
'passed' => $passed,
'match_strategy' => $strategy,
'details' => $details,
];
}
/**
* Normalizes many database rows.
*
* @param mixed $rows
* @return array<int,array<string,mixed>>
*/
private function normalize_rows($rows): array
{
if (!is_array($rows)) {
return [];
}
return array_map([$this, 'normalize_row'], $rows);
}
/**
* Adds decoded JSON helper fields to a database row.
*
* @param array<string,mixed> $row
* @return array<string,mixed>
*/
private function normalize_row(array $row): array
{
$variables = json_decode((string) ($row['variables_json'] ?? ''), true);
$last_result = json_decode((string) ($row['last_result_json'] ?? ''), true);
$row['variables'] = is_array($variables) ? $variables : [];
$row['last_result'] = is_array($last_result) ? $last_result : [];
return $row;
}
/**
* Sanitizes variables without discarding useful structured values.
*/
private function sanitize_variables(array $variables): array
{
$clean = [];
foreach ($variables as $key => $value) {
$clean[sanitize_key((string) $key)] = is_scalar($value)
? sanitize_textarea_field((string) $value)
: $value;
}
return $clean;
}
/**
* Collapses whitespace and casing for stable text comparison.
*/
private function normalize_text(string $value): string
{
return strtolower(trim((string) preg_replace('/\s+/', ' ', $value)));
}
}