198 lines
6.3 KiB
Go
198 lines
6.3 KiB
Go
// Source-level contract tests for the coverage repository SQL.
|
|
// No real database connection is used.
|
|
package postgres
|
|
|
|
import (
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func readCoverageSource(t *testing.T) string {
|
|
t.Helper()
|
|
data, err := os.ReadFile("coverage.go")
|
|
if err != nil {
|
|
t.Fatalf("read coverage.go: %v", err)
|
|
}
|
|
return string(data)
|
|
}
|
|
|
|
func extractSQLFromCoverage(t *testing.T) []string {
|
|
t.Helper()
|
|
src := readCoverageSource(t)
|
|
var sqls []string
|
|
re := regexp.MustCompile("`([^`]*)`")
|
|
for _, m := range re.FindAllStringSubmatch(src, -1) {
|
|
upper := strings.ToUpper(m[1])
|
|
if strings.Contains(upper, "SELECT") || strings.Contains(upper, "UPDATE") ||
|
|
strings.Contains(upper, "INSERT") || strings.Contains(upper, "DELETE") {
|
|
sqls = append(sqls, m[1])
|
|
}
|
|
}
|
|
return sqls
|
|
}
|
|
|
|
func TestCoverageRepoInsertsIntoCoverageDecisions(t *testing.T) {
|
|
// Find the INSERT INTO coverage_decisions SQL in coverage.go
|
|
sqls := extractSQLFromCoverage(t)
|
|
var found string
|
|
for _, sql := range sqls {
|
|
upper := strings.ToUpper(sql)
|
|
if strings.Contains(upper, "INSERT INTO COVERAGE_DECISIONS") {
|
|
found = sql
|
|
break
|
|
}
|
|
}
|
|
if found == "" {
|
|
t.Fatal("INSERT INTO coverage_decisions SQL not found in coverage.go")
|
|
}
|
|
|
|
requiredCols := []string{
|
|
"id", "tenant_key", "channel", "turn_id",
|
|
"evaluator_version", "verdict", "reason_code", "reference_count", "top_score",
|
|
}
|
|
upper := strings.ToUpper(found)
|
|
for _, col := range requiredCols {
|
|
if !strings.Contains(upper, strings.ToUpper(col)) {
|
|
t.Errorf("INSERT INTO coverage_decisions missing column %q", col)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCoverageRepoReplayReturnsPersistedDecisionIDForSupportCase(t *testing.T) {
|
|
sqls := extractSQLFromCoverage(t)
|
|
var decisionInsert string
|
|
for _, sql := range sqls {
|
|
if strings.Contains(strings.ToUpper(sql), "INSERT INTO COVERAGE_DECISIONS") {
|
|
decisionInsert = strings.ToUpper(sql)
|
|
break
|
|
}
|
|
}
|
|
if decisionInsert == "" {
|
|
t.Fatal("INSERT INTO coverage_decisions SQL not found")
|
|
}
|
|
if !strings.Contains(decisionInsert, "ON CONFLICT (TENANT_KEY, CHANNEL, TURN_ID)") {
|
|
t.Fatal("decision upsert must use the scoped idempotency key")
|
|
}
|
|
if !strings.Contains(decisionInsert, "DO UPDATE") || !strings.Contains(decisionInsert, "RETURNING ID") {
|
|
t.Fatal("decision upsert must return the persisted ID on both insert and replay")
|
|
}
|
|
|
|
src := readCoverageSource(t)
|
|
if !strings.Contains(src, "persistedDecisionID") {
|
|
t.Fatal("support case must use the persisted decision ID")
|
|
}
|
|
if !strings.Contains(src, "d.TurnID, persistedDecisionID") {
|
|
t.Fatal("support case INSERT must reference persistedDecisionID, not a new conflicting ID")
|
|
}
|
|
}
|
|
|
|
func TestCoverageRepoInsertsSupportCaseOnlyForUncovered(t *testing.T) {
|
|
sqls := extractSQLFromCoverage(t)
|
|
var insertSQL string
|
|
for _, sql := range sqls {
|
|
upper := strings.ToUpper(sql)
|
|
if strings.Contains(upper, "INSERT INTO SUPPORT_CASES") {
|
|
insertSQL = sql
|
|
break
|
|
}
|
|
}
|
|
if insertSQL == "" {
|
|
t.Fatal("INSERT INTO support_cases SQL not found in coverage.go")
|
|
}
|
|
|
|
upper := strings.ToUpper(insertSQL)
|
|
requiredCols := []string{
|
|
"id", "tenant_key", "channel", "turn_id",
|
|
"coverage_decision_id", "status",
|
|
}
|
|
for _, col := range requiredCols {
|
|
if !strings.Contains(upper, strings.ToUpper(col)) {
|
|
t.Errorf("INSERT INTO support_cases missing column %q", col)
|
|
}
|
|
}
|
|
// Must have ON CONFLICT for idempotency
|
|
if !strings.Contains(upper, "ON CONFLICT") {
|
|
t.Error("support_cases INSERT must use ON CONFLICT for idempotency")
|
|
}
|
|
if !strings.Contains(upper, "DO NOTHING") {
|
|
t.Error("support_cases ON CONFLICT must use DO NOTHING")
|
|
}
|
|
|
|
// Check that the support_cases INSERT is guarded by a verdict check
|
|
src := readCoverageSource(t)
|
|
if !strings.Contains(src, "VerdictUncovered") {
|
|
t.Error("support_cases INSERT must be guarded by uncovered verdict check")
|
|
}
|
|
}
|
|
|
|
func TestCoverageRepoFAQExportStatusFollowsRuntimeFlag(t *testing.T) {
|
|
if got := faqExportStatus(false); got != "not_applicable" {
|
|
t.Fatalf("disabled status=%q", got)
|
|
}
|
|
if got := faqExportStatus(true); got != "pending" {
|
|
t.Fatalf("enabled status=%q", got)
|
|
}
|
|
}
|
|
|
|
func TestCoverageRepoUsesSingleTransaction(t *testing.T) {
|
|
src := readCoverageSource(t)
|
|
// Must use BeginTx
|
|
if !strings.Contains(src, "BeginTx") {
|
|
t.Error("SaveCoverageDecision must use a transaction (BeginTx)")
|
|
}
|
|
// Must commit
|
|
if !strings.Contains(src, "Commit(ctx)") {
|
|
t.Error("SaveCoverageDecision must commit the transaction")
|
|
}
|
|
// Must rollback on error
|
|
if !strings.Contains(src, "Rollback(ctx)") {
|
|
t.Error("SaveCoverageDecision must rollback on error")
|
|
}
|
|
}
|
|
|
|
func TestCoverageRepoNoReservedWords(t *testing.T) {
|
|
sqls := extractSQLFromCoverage(t)
|
|
for _, sql := range sqls {
|
|
upper := strings.ToUpper(sql)
|
|
// Check for bare "references" as column name (not references_json)
|
|
// Remove references_json first
|
|
scrubbed := regexp.MustCompile("(?i)\\breferences_json\\b").ReplaceAllString(upper, "")
|
|
// Also remove REFERENCES as foreign key keyword
|
|
scrubbed = regexp.MustCompile("(?i)\\bREFERENCES\\s+(COVERAGE_DECISIONS|TURNS)\\b").ReplaceAllString(scrubbed, "")
|
|
if regexp.MustCompile("(?i)\\bREFERENCES\\b").MatchString(scrubbed) {
|
|
t.Errorf("SQL uses reserved word 'references' as identifier: %s", sql)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCoverageRepoNoQuestionAnswerBodies(t *testing.T) {
|
|
sqls := extractSQLFromCoverage(t)
|
|
joined := strings.ToUpper(strings.Join(sqls, " "))
|
|
for _, forbidden := range []string{"QUESTION", "ANSWER_TEXT", "REFERENCE_CONTENT", "ANSWER_CONTENT"} {
|
|
if strings.Contains(joined, forbidden) {
|
|
t.Errorf("coverage repository SQL must not dupe question/answer/reference bodies: found %q", forbidden)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCoverageRepoSavesTenantChannelTurnScope(t *testing.T) {
|
|
src := readCoverageSource(t)
|
|
// Must pass tenant, channel, turn values to the INSERT
|
|
if !strings.Contains(src, "d.TenantKey") || !strings.Contains(src, "d.Channel") || !strings.Contains(src, "d.TurnID") {
|
|
t.Error("SaveCoverageDecision must use d.TenantKey, d.Channel, d.TurnID for tenant/channel/turn scope")
|
|
}
|
|
if !strings.Contains(src, "d.ReferenceCount") {
|
|
t.Error("SaveCoverageDecision must persist d.ReferenceCount")
|
|
}
|
|
}
|
|
|
|
func TestCoverageRepoNilSafe(t *testing.T) {
|
|
src := readCoverageSource(t)
|
|
// Must check for nil store/pool
|
|
if !strings.Contains(src, "s == nil") || !strings.Contains(src, "s.pool == nil") {
|
|
t.Error("SaveCoverageDecision must guard against nil store/pool")
|
|
}
|
|
}
|