Skip to content

Commit 0b34de1

Browse files
authored
persistent failed status when scanning multiple files (#249)
* add tests for exit code * fmt * fix wrong query
1 parent 5faf41a commit 0b34de1

File tree

5 files changed

+281
-7
lines changed

5 files changed

+281
-7
lines changed

src/core/execute.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ pub async fn execute(queries: &HashMap<PathBuf, Vec<SQL>>, handler: &Handler) ->
2626
let (explain_failed, ts_query) = &connection.prepare(sql, should_generate_types, handler).await?;
2727

2828
// If any prepare statement fails, we should set the failed flag as true
29-
failed = *explain_failed;
29+
// Use OR to accumulate failures - once failed, it stays failed
30+
failed = failed || *explain_failed;
3031

3132
if *should_generate_types {
3233
let ts_query = &ts_query.clone().expect("Failed to generate types from query");

tests/demo/select/select.queries.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ export interface ISelectSql20Query {
240240
export type SelectSql21Params = [];
241241

242242
export interface ISelectSql21Result {
243-
desc: string;
243+
description: string;
244244
}
245245

246246
export interface ISelectSql21Query {
@@ -306,7 +306,7 @@ export interface ISelectSql26Query {
306306
export type SelectSql27Params = [];
307307

308308
export interface ISelectSql27Result {
309-
rank: string;
309+
guildRank: string;
310310
}
311311

312312
export interface ISelectSql27Query {

tests/demo/select/select.snapshot.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ export interface ISelectSql20Query {
240240
export type SelectSql21Params = [];
241241

242242
export interface ISelectSql21Result {
243-
desc: string;
243+
description: string;
244244
}
245245

246246
export interface ISelectSql21Query {
@@ -306,7 +306,7 @@ export interface ISelectSql26Query {
306306
export type SelectSql27Params = [];
307307

308308
export interface ISelectSql27Result {
309-
rank: string;
309+
guildRank: string;
310310
}
311311

312312
export interface ISelectSql27Query {

tests/demo/select/select.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ SELECT IFNULL(gold, 0.0) AS gold_amount FROM characters;
113113
// SELECT IFNULL with TEXT type
114114
const selectSql21 = sql`
115115
-- @db: db_mysql
116-
SELECT IFNULL(description, 'No description') AS desc FROM factions;
116+
SELECT IFNULL(description, 'No description') AS description FROM factions;
117117
`
118118

119119
// SELECT COALESCE with multiple arguments
@@ -149,7 +149,7 @@ SELECT NULLIF(quantity, 0) AS inv_quantity FROM inventory;
149149
// SELECT NVL with VARCHAR (Oracle-style, but should work)
150150
const selectSql27 = sql`
151151
-- @db: db_mysql
152-
SELECT NVL(guild_rank, 'Member') AS rank FROM guild_members;
152+
SELECT IFNULL(guild_rank, 'Member') AS guild_rank FROM guild_members;
153153
`
154154

155155
// SELECT IFNULL with compound identifier

tests/exit_code_on_error.rs

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
#[cfg(test)]
2+
mod exit_code_tests {
3+
use assert_cmd::prelude::*;
4+
use std::fs;
5+
use std::io::Write;
6+
use std::process::Command;
7+
use tempfile::tempdir;
8+
9+
/// This test verifies that even when there are multiple SQL queries with mixed
10+
/// success/failure, the tool still exits with code 1
11+
#[test]
12+
fn should_exit_with_code_1_when_sql_errors_detected() -> Result<(), Box<dyn std::error::Error>> {
13+
// SETUP
14+
let dir = tempdir()?;
15+
let parent_path = dir.path();
16+
let file_path = parent_path.join("index.ts");
17+
18+
let index_content = r#"
19+
import { sql } from "sqlx-ts";
20+
21+
// This should succeed
22+
const validQuery = sql`SELECT id, name FROM characters WHERE id = $1;`;
23+
24+
// This should fail - unknown table
25+
const invalidQuery = sql`SELECT * FROM unknown_table;`;
26+
27+
// Another valid query after the failure
28+
const anotherValidQuery = sql`SELECT * FROM inventory WHERE character_id = $1;`;
29+
"#;
30+
let mut temp_file = fs::File::create(&file_path)?;
31+
writeln!(temp_file, "{}", index_content)?;
32+
33+
// EXECUTE
34+
let mut cmd = Command::cargo_bin("sqlx-ts").unwrap();
35+
36+
cmd
37+
.arg(parent_path.to_str().unwrap())
38+
.arg("--ext=ts")
39+
.arg("--db-type=postgres")
40+
.arg("--db-host=127.0.0.1")
41+
.arg("--db-port=54321")
42+
.arg("--db-user=postgres")
43+
.arg("--db-pass=postgres")
44+
.arg("--db-name=postgres");
45+
46+
// ASSERT - should exit with non-zero code due to the error
47+
cmd
48+
.assert()
49+
.failure()
50+
.stderr(predicates::str::contains("relation \"unknown_table\" does not exist"))
51+
.stderr(predicates::str::contains("SQLs failed to compile!"));
52+
53+
Ok(())
54+
}
55+
56+
/// Test that when all queries succeed, exit code is 0
57+
#[test]
58+
fn should_exit_with_code_0_when_no_errors() -> Result<(), Box<dyn std::error::Error>> {
59+
// SETUP
60+
let dir = tempdir()?;
61+
let parent_path = dir.path();
62+
let file_path = parent_path.join("valid.ts");
63+
64+
let index_content = r#"
65+
import { sql } from "sqlx-ts";
66+
67+
const query1 = sql`SELECT id, name FROM characters WHERE id = $1;`;
68+
const query2 = sql`SELECT * FROM inventory WHERE character_id = $1;`;
69+
const query3 = sql`SELECT * FROM items WHERE id = $1;`;
70+
"#;
71+
let mut temp_file = fs::File::create(&file_path)?;
72+
writeln!(temp_file, "{}", index_content)?;
73+
74+
// EXECUTE
75+
let mut cmd = Command::cargo_bin("sqlx-ts").unwrap();
76+
77+
cmd
78+
.arg(parent_path.to_str().unwrap())
79+
.arg("--ext=ts")
80+
.arg("--db-type=postgres")
81+
.arg("--db-host=127.0.0.1")
82+
.arg("--db-port=54321")
83+
.arg("--db-user=postgres")
84+
.arg("--db-pass=postgres")
85+
.arg("--db-name=postgres");
86+
87+
// ASSERT - should succeed
88+
cmd
89+
.assert()
90+
.success()
91+
.stdout(predicates::str::contains("No SQL errors detected!"));
92+
93+
Ok(())
94+
}
95+
96+
/// Test with many successes and one failure in the middle
97+
/// Pattern: 5 successes -> 1 failure -> 5 successes
98+
/// Expected: exit code 1
99+
#[test]
100+
fn should_fail_with_many_successes_and_one_failure_in_middle() -> Result<(), Box<dyn std::error::Error>> {
101+
// SETUP
102+
let dir = tempdir()?;
103+
let parent_path = dir.path();
104+
let file_path = parent_path.join("one_failure.ts");
105+
106+
let index_content = r#"
107+
import { sql } from "sqlx-ts";
108+
109+
const query1 = sql`SELECT id FROM characters WHERE id = $1;`;
110+
const query2 = sql`SELECT name FROM characters WHERE id = $1;`;
111+
const query3 = sql`SELECT * FROM inventory WHERE id = $1;`;
112+
const query4 = sql`SELECT * FROM items WHERE id = $1;`;
113+
const query5 = sql`SELECT quantity FROM inventory WHERE id = $1;`;
114+
115+
// Single failure in the middle
116+
const failedQuery = sql`SELECT * FROM this_table_does_not_exist;`;
117+
118+
const query6 = sql`SELECT rarity FROM items WHERE id = $1;`;
119+
const query7 = sql`SELECT character_id FROM inventory WHERE id = $1;`;
120+
const query8 = sql`SELECT flavor_text FROM items WHERE id = $1;`;
121+
const query9 = sql`SELECT id, quantity FROM inventory WHERE character_id = $1;`;
122+
const query10 = sql`SELECT id, name FROM characters LIMIT 10;`;
123+
"#;
124+
let mut temp_file = fs::File::create(&file_path)?;
125+
writeln!(temp_file, "{}", index_content)?;
126+
127+
// EXECUTE
128+
let mut cmd = Command::cargo_bin("sqlx-ts").unwrap();
129+
130+
cmd
131+
.arg(parent_path.to_str().unwrap())
132+
.arg("--ext=ts")
133+
.arg("--db-type=postgres")
134+
.arg("--db-host=127.0.0.1")
135+
.arg("--db-port=54321")
136+
.arg("--db-user=postgres")
137+
.arg("--db-pass=postgres")
138+
.arg("--db-name=postgres");
139+
140+
// ASSERT - should fail despite 10 successes and only 1 failure
141+
cmd
142+
.assert()
143+
.failure()
144+
.stderr(predicates::str::contains(
145+
"relation \"this_table_does_not_exist\" does not exist",
146+
))
147+
.stderr(predicates::str::contains("SQLs failed to compile!"));
148+
149+
Ok(())
150+
}
151+
152+
/// Test with multiple files: one success file and one failure file
153+
/// Expected: exit code 1
154+
#[test]
155+
fn should_fail_with_multiple_files_one_success_one_failure() -> Result<(), Box<dyn std::error::Error>> {
156+
// SETUP
157+
let dir = tempdir()?;
158+
let parent_path = dir.path();
159+
160+
// File 1: All successful queries
161+
let file1_path = parent_path.join("success.ts");
162+
let file1_content = r#"
163+
import { sql } from "sqlx-ts";
164+
165+
const query1 = sql`SELECT id, name FROM characters WHERE id = $1;`;
166+
const query2 = sql`SELECT * FROM inventory WHERE character_id = $1;`;
167+
const query3 = sql`SELECT * FROM items WHERE id = $1;`;
168+
"#;
169+
let mut file1 = fs::File::create(&file1_path)?;
170+
writeln!(file1, "{}", file1_content)?;
171+
172+
// File 2: Contains failures
173+
let file2_path = parent_path.join("failure.ts");
174+
let file2_content = r#"
175+
import { sql } from "sqlx-ts";
176+
177+
const failQuery1 = sql`SELECT * FROM nonexistent_table;`;
178+
const failQuery2 = sql`SELECT * FROM another_missing_table;`;
179+
"#;
180+
let mut file2 = fs::File::create(&file2_path)?;
181+
writeln!(file2, "{}", file2_content)?;
182+
183+
// EXECUTE
184+
let mut cmd = Command::cargo_bin("sqlx-ts").unwrap();
185+
186+
cmd
187+
.arg(parent_path.to_str().unwrap())
188+
.arg("--ext=ts")
189+
.arg("--db-type=postgres")
190+
.arg("--db-host=127.0.0.1")
191+
.arg("--db-port=54321")
192+
.arg("--db-user=postgres")
193+
.arg("--db-pass=postgres")
194+
.arg("--db-name=postgres");
195+
196+
// ASSERT - should fail because file2 has errors
197+
cmd
198+
.assert()
199+
.failure()
200+
.stderr(predicates::str::contains(
201+
"relation \"nonexistent_table\" does not exist",
202+
))
203+
.stderr(predicates::str::contains(
204+
"relation \"another_missing_table\" does not exist",
205+
))
206+
.stderr(predicates::str::contains("SQLs failed to compile!"));
207+
208+
Ok(())
209+
}
210+
211+
/// Test with multiple files: all files contain successful queries
212+
/// Expected: exit code 0
213+
#[test]
214+
fn should_succeed_with_multiple_files_all_successful() -> Result<(), Box<dyn std::error::Error>> {
215+
// SETUP
216+
let dir = tempdir()?;
217+
let parent_path = dir.path();
218+
219+
// File 1: Successful queries
220+
let file1_path = parent_path.join("queries1.ts");
221+
let file1_content = r#"
222+
import { sql } from "sqlx-ts";
223+
224+
const query1 = sql`SELECT id FROM characters WHERE id = $1;`;
225+
const query2 = sql`SELECT name FROM characters WHERE id = $1;`;
226+
"#;
227+
let mut file1 = fs::File::create(&file1_path)?;
228+
writeln!(file1, "{}", file1_content)?;
229+
230+
// File 2: More successful queries
231+
let file2_path = parent_path.join("queries2.ts");
232+
let file2_content = r#"
233+
import { sql } from "sqlx-ts";
234+
235+
const query3 = sql`SELECT * FROM inventory WHERE id = $1;`;
236+
const query4 = sql`SELECT * FROM items WHERE id = $1;`;
237+
"#;
238+
let mut file2 = fs::File::create(&file2_path)?;
239+
writeln!(file2, "{}", file2_content)?;
240+
241+
// File 3: Even more successful queries
242+
let file3_path = parent_path.join("queries3.ts");
243+
let file3_content = r#"
244+
import { sql } from "sqlx-ts";
245+
246+
const query5 = sql`SELECT quantity FROM inventory WHERE character_id = $1;`;
247+
const query6 = sql`SELECT rarity FROM items WHERE id = $1;`;
248+
"#;
249+
let mut file3 = fs::File::create(&file3_path)?;
250+
writeln!(file3, "{}", file3_content)?;
251+
252+
// EXECUTE
253+
let mut cmd = Command::cargo_bin("sqlx-ts").unwrap();
254+
255+
cmd
256+
.arg(parent_path.to_str().unwrap())
257+
.arg("--ext=ts")
258+
.arg("--db-type=postgres")
259+
.arg("--db-host=127.0.0.1")
260+
.arg("--db-port=54321")
261+
.arg("--db-user=postgres")
262+
.arg("--db-pass=postgres")
263+
.arg("--db-name=postgres");
264+
265+
// ASSERT - should succeed
266+
cmd
267+
.assert()
268+
.success()
269+
.stdout(predicates::str::contains("No SQL errors detected!"));
270+
271+
Ok(())
272+
}
273+
}

0 commit comments

Comments
 (0)