package shadowdb_test import ( "errors" "testing" "git.flytoex.net/yuanwei/flyto-agent/pkg/shadowdb" ) func TestEnforceSessionFilter_Happy(t *testing.T) { cases := []string{ "UPDATE t SET x=1 WHERE session_id=?", "UPDATE t SET x=1 WHERE session_id = ?", "UPDATE t SET x=1 WHERE session_id = ?", "UPDATE t SET x=1 WHERE sku='S1' AND session_id=?", "SELECT * FROM t WHERE session_id=? AND sku='S1'", // Case-insensitive on the identifier itself (SQL is // conventionally uppercase keywords, but session_id is a // column name that can be written either way). "SELECT * FROM t WHERE SESSION_ID=?", "SELECT * FROM t WHERE Session_Id = ?", // Newline before filter. "UPDATE t\nSET x=1\nWHERE session_id=?", } for _, sql := range cases { if err := shadowdb.EnforceSessionFilter(sql); err != nil { t.Errorf("EnforceSessionFilter(%q) = %v, want nil", sql, err) } } } func TestEnforceSessionFilter_Missing(t *testing.T) { cases := []string{ "UPDATE t SET x=1 WHERE sku='S1'", "SELECT * FROM t", "DELETE FROM t", // session_id present but with literal (no placeholder) -- reject. // Literal binding bypasses parameterization; force ? to keep the // enforcement aligned with parameter-bound execution. "UPDATE t SET x=1 WHERE session_id='abc'", // IN (?) form -- outside scope (docs say use platform-layer // validator). Reject. "UPDATE t SET x=1 WHERE session_id IN (?)", } for _, sql := range cases { err := shadowdb.EnforceSessionFilter(sql) if !errors.Is(err, shadowdb.ErrMissingSessionFilter) { t.Errorf("EnforceSessionFilter(%q) = %v, want ErrMissingSessionFilter", sql, err) } } } func TestEnforceSessionFilter_IgnoresStringLiterals(t *testing.T) { // "session_id=?" appearing inside a string literal must NOT // count as filter presence. These all lack a real filter. cases := []string{ `UPDATE logs SET msg='session_id=? missed' WHERE id=1`, `INSERT INTO logs VALUES ('session_id = ?')`, // SQL-standard doubled quote inside string `INSERT INTO logs VALUES ('it''s session_id=? time')`, // Double-quoted identifier (ANSI) that happens to contain =? `SELECT "session_id=?col" FROM t`, } for _, sql := range cases { err := shadowdb.EnforceSessionFilter(sql) if !errors.Is(err, shadowdb.ErrMissingSessionFilter) { t.Errorf("should reject (string literal only): %q; got %v", sql, err) } } } func TestEnforceSessionFilter_IgnoresComments(t *testing.T) { cases := []string{ // line comment with filter -- must NOT count "UPDATE t SET x=1 WHERE sku=? -- session_id=?\n", // block comment with filter -- must NOT count "UPDATE t SET x=1 WHERE sku=? /* session_id=? */", // multi-line block comment "UPDATE t SET x=1\n/* session_id=?\n multi-line */\nWHERE sku=?", } for _, sql := range cases { err := shadowdb.EnforceSessionFilter(sql) if !errors.Is(err, shadowdb.ErrMissingSessionFilter) { t.Errorf("should reject (filter in comment): %q; got %v", sql, err) } } } func TestEnforceSessionFilter_CommentInSqlNotSuppressingRealFilter(t *testing.T) { // Real filter plus an unrelated comment -- should pass. cases := []string{ "UPDATE t SET x=1 /* audit log */ WHERE session_id=?", "-- top-level comment\nUPDATE t SET x=1 WHERE session_id=?", "UPDATE t SET x=1 WHERE session_id=? -- trailing comment", } for _, sql := range cases { if err := shadowdb.EnforceSessionFilter(sql); err != nil { t.Errorf("should accept (real filter + unrelated comment): %q; got %v", sql, err) } } } func TestEnforceSessionFilter_UnterminatedStringSafe(t *testing.T) { // Malformed SQL (unterminated string / comment). Not our job // to validate syntax; just make sure we don't panic and return // a sensible result. An unterminated string swallows the rest // of the SQL -- if the filter is inside, it's swallowed too // and we'll reject. That's acceptable: malformed SQL won't // execute either way. cases := []string{ "UPDATE t SET x='broken WHERE session_id=?", // unterminated ' `UPDATE t SET x="broken WHERE session_id=?`, // unterminated " "UPDATE t SET x=1 /* unterminated block WHERE session_id=?", } for _, sql := range cases { // Must not panic; result is allowed to be either (accept or // reject) because the SQL is malformed. We only assert // no-panic by running it. _ = shadowdb.EnforceSessionFilter(sql) } }