Files
dobotai-platform/internal/store/postgres/coverage_contract_test.go
T

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")
}
}