Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 157 additions & 18 deletions Sources/StructuredQueriesTestSupport/AssertQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import StructuredQueriesCore
/// - snapshotTrailingClosureOffset: The trailing closure offset of the `sql` snapshot. Defaults
/// to `1` for invoking this helper directly, but if you write a wrapper function that automates
/// the `execute` trailing closure, you should pass `0` instead.
/// - assertSql: Whether to snapshot the SQL fragment. Defaults to true, but you may prefer false
/// if you write a wrapper function for other purposes.
/// - fileID: The source `#fileID` associated with the assertion.
/// - filePath: The source `#filePath` associated with the assertion.
/// - function: The source `#function` associated with the assertion
Expand All @@ -56,27 +58,30 @@ public func assertQuery<each V: QueryRepresentable, S: Statement<(repeat each V)
sql: (() -> String)? = nil,
results: (() -> String)? = nil,
snapshotTrailingClosureOffset: Int = 1,
assertSql: Bool = true,
fileID: StaticString = #fileID,
filePath: StaticString = #filePath,
function: StaticString = #function,
line: UInt = #line,
column: UInt = #column
) {
assertInlineSnapshot(
of: query,
as: .sql,
message: "Query did not match",
syntaxDescriptor: InlineSnapshotSyntaxDescriptor(
trailingClosureLabel: "sql",
trailingClosureOffset: snapshotTrailingClosureOffset
),
matches: sql,
fileID: fileID,
file: filePath,
function: function,
line: line,
column: column
)
if assertSql {
assertInlineSnapshot(
of: query,
as: .sql,
message: "Query did not match",
syntaxDescriptor: InlineSnapshotSyntaxDescriptor(
trailingClosureLabel: "sql",
trailingClosureOffset: snapshotTrailingClosureOffset
),
matches: sql,
fileID: fileID,
file: filePath,
function: function,
line: line,
column: column
)
}
do {
let rows = try execute(query)
var table = ""
Expand All @@ -88,7 +93,7 @@ public func assertQuery<each V: QueryRepresentable, S: Statement<(repeat each V)
message: "Results did not match",
syntaxDescriptor: InlineSnapshotSyntaxDescriptor(
trailingClosureLabel: "results",
trailingClosureOffset: snapshotTrailingClosureOffset + 1
trailingClosureOffset: assertSql ? snapshotTrailingClosureOffset + 1 : snapshotTrailingClosureOffset
),
matches: results,
fileID: fileID,
Expand All @@ -104,7 +109,7 @@ public func assertQuery<each V: QueryRepresentable, S: Statement<(repeat each V)
message: "Results expected to be empty",
syntaxDescriptor: InlineSnapshotSyntaxDescriptor(
trailingClosureLabel: "results",
trailingClosureOffset: snapshotTrailingClosureOffset + 1
trailingClosureOffset: assertSql ? snapshotTrailingClosureOffset + 1 : snapshotTrailingClosureOffset
),
matches: results,
fileID: fileID,
Expand All @@ -121,7 +126,7 @@ public func assertQuery<each V: QueryRepresentable, S: Statement<(repeat each V)
message: "Results did not match",
syntaxDescriptor: InlineSnapshotSyntaxDescriptor(
trailingClosureLabel: "results",
trailingClosureOffset: snapshotTrailingClosureOffset + 1
trailingClosureOffset: assertSql ? snapshotTrailingClosureOffset + 1 : snapshotTrailingClosureOffset
),
matches: results,
fileID: fileID,
Expand Down Expand Up @@ -207,6 +212,140 @@ public func assertQuery<S: SelectStatement, each J: Table>(
)
}

/// A snapshot testing helper for database content.
///
/// This helper can be used to generate snapshots of results of the query decoded back into Swift.
///
/// ```swift
/// assertSelect(
/// Reminder.select(\.title).order(by: \.title)
/// ) {
/// try db.execute($0)
/// } results: {
/// """
/// ┌────────────────────────────┐
/// │ "Buy concert tickets" │
/// │ "Call accountant" │
/// │ "Doctor appointment" │
/// │ "Get laundry" │
/// │ "Groceries" │
/// │ "Haircut" │
/// │ "Pick up kids from school" │
/// │ "Send weekly emails" │
/// │ "Take a walk" │
/// │ "Take out trash" │
/// └────────────────────────────┘
/// """
/// }
/// ```
///
/// - Parameters:
/// - query: A statement.
/// - execute: A closure responsible for executing the query and returning the results.
/// - results: A snapshot of the results.
/// - snapshotTrailingClosureOffset: The trailing closure offset of the `sql` snapshot. Defaults
/// to `1` for invoking this helper directly, but if you write a wrapper function that automates
/// the `execute` trailing closure, you should pass `0` instead.
/// - fileID: The source `#fileID` associated with the assertion.
/// - filePath: The source `#filePath` associated with the assertion.
/// - function: The source `#function` associated with the assertion
/// - line: The source `#line` associated with the assertion.
/// - column: The source `#column` associated with the assertion.
@_disfavoredOverload
public func assertSelect<each V: QueryRepresentable, S: Statement<(repeat each V)>>(
_ query: S,
execute: (S) throws -> [(repeat (each V).QueryOutput)],
results: (() -> String)? = nil,
snapshotTrailingClosureOffset: Int = 1,
assertSql: Bool = true,
fileID: StaticString = #fileID,
filePath: StaticString = #filePath,
function: StaticString = #function,
line: UInt = #line,
column: UInt = #column
) {
assertQuery(
query,
execute: execute,
sql: nil,
results: results,
snapshotTrailingClosureOffset: snapshotTrailingClosureOffset,
assertSql: false,
fileID: fileID,
filePath: filePath,
function: function,
line: line,
column: column
)
}

/// A snapshot testing helper for database content.
///
/// This helper can be used to generate snapshots of results of the query decoded back into Swift.
///
/// ```swift
/// assertSelect(
/// Reminder.select(\.title).order(by: \.title)
/// ) {
/// try db.execute($0)
/// } results: {
/// """
/// ┌────────────────────────────┐
/// │ "Buy concert tickets" │
/// │ "Call accountant" │
/// │ "Doctor appointment" │
/// │ "Get laundry" │
/// │ "Groceries" │
/// │ "Haircut" │
/// │ "Pick up kids from school" │
/// │ "Send weekly emails" │
/// │ "Take a walk" │
/// │ "Take out trash" │
/// └────────────────────────────┘
/// """
/// }
/// ```
///
/// - Parameters:
/// - query: A statement.
/// - execute: A closure responsible for executing the query and returning the results.
/// - results: A snapshot of the results.
/// - snapshotTrailingClosureOffset: The trailing closure offset of the `sql` snapshot. Defaults
/// to `1` for invoking this helper directly, but if you write a wrapper function that automates
/// the `execute` trailing closure, you should pass `0` instead.
/// - fileID: The source `#fileID` associated with the assertion.
/// - filePath: The source `#filePath` associated with the assertion.
/// - function: The source `#function` associated with the assertion
/// - line: The source `#line` associated with the assertion.
/// - column: The source `#column` associated with the assertion.
public func assertSelect<S: SelectStatement, each J: Table>(
_ query: S,
execute: (Select<(S.From, repeat each J), S.From, (repeat each J)>) throws -> [(
S.From.QueryOutput, repeat (each J).QueryOutput
)],
results: (() -> String)? = nil,
snapshotTrailingClosureOffset: Int = 1,
fileID: StaticString = #fileID,
filePath: StaticString = #filePath,
function: StaticString = #function,
line: UInt = #line,
column: UInt = #column
) where S.QueryValue == (), S.Joins == (repeat each J) {
assertQuery(
query.selectStar(),
execute: execute,
sql: nil,
results: results,
snapshotTrailingClosureOffset: snapshotTrailingClosureOffset,
assertSql: false,
fileID: fileID,
filePath: filePath,
function: function,
line: line,
column: column
)
}

private func printTable<each C>(_ rows: [(repeat each C)], to output: inout some TextOutputStream) {
var maxColumnSpan: [Int] = []
var hasMultiLineRows = false
Expand Down
109 changes: 109 additions & 0 deletions Tests/StructuredQueriesTests/AssertQueryTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import Dependencies
import StructuredQueries
import StructuredQueriesSQLite
import StructuredQueriesTestSupport
import Testing

extension SnapshotTests {
@Suite
struct AssertQueryTests {
@Dependency(\.defaultDatabase) var db
@Test func assertQueryBasicType() {
StructuredQueriesTestSupport.assertQuery(
Reminder.all
.select { ($0.id, $0.assignedUserID) }
.limit(3)
.order(by: \.id)
) {
try db.execute($0)
} sql: {
"""
SELECT "reminders"."id", "reminders"."assignedUserID"
FROM "reminders"
ORDER BY "reminders"."id"
LIMIT 3
"""
} results: {
"""
┌───┬─────┐
│ 1 │ 1 │
│ 2 │ nil │
│ 3 │ nil │
└───┴─────┘
"""
}
}
@Test func assertQueryComplexType() {
StructuredQueriesTestSupport.assertQuery(
Reminder.where { $0.id == 1 }
) {
try db.execute($0)
} sql: {
"""
SELECT "reminders"."id", "reminders"."assignedUserID", "reminders"."dueDate", "reminders"."isCompleted", "reminders"."isFlagged", "reminders"."notes", "reminders"."priority", "reminders"."remindersListID", "reminders"."title", "reminders"."updatedAt"
FROM "reminders"
WHERE ("reminders"."id" = 1)
"""
} results: {
"""
┌─────────────────────────────────────────────┐
│ Reminder( │
│ id: 1, │
│ assignedUserID: 1, │
│ dueDate: Date(2001-01-01T00:00:00.000Z), │
│ isCompleted: false, │
│ isFlagged: false, │
│ notes: "Milk, Eggs, Apples", │
│ priority: nil, │
│ remindersListID: 1, │
│ title: "Groceries", │
│ updatedAt: Date(2040-02-14T23:31:30.000Z) │
│ ) │
└─────────────────────────────────────────────┘
"""
}
}
@Test func assertSelectBasicType() {
StructuredQueriesTestSupport.assertSelect(
Reminder.all
.select { ($0.id, $0.assignedUserID) }
.limit(3)
.order(by: \.id)
) {
try db.execute($0)
} results: {
"""
┌───┬─────┐
│ 1 │ 1 │
│ 2 │ nil │
│ 3 │ nil │
└───┴─────┘
"""
}
}
@Test func assertSelectComplexType() {
StructuredQueriesTestSupport.assertSelect(
Reminder.where { $0.id == 1 }
) {
try db.execute($0)
} results: {
"""
┌─────────────────────────────────────────────┐
│ Reminder( │
│ id: 1, │
│ assignedUserID: 1, │
│ dueDate: Date(2001-01-01T00:00:00.000Z), │
│ isCompleted: false, │
│ isFlagged: false, │
│ notes: "Milk, Eggs, Apples", │
│ priority: nil, │
│ remindersListID: 1, │
│ title: "Groceries", │
│ updatedAt: Date(2040-02-14T23:31:30.000Z) │
│ ) │
└─────────────────────────────────────────────┘
"""
}
}
}
}