SwiftDBAI: natural language queries for any SQLite database
Drop-in SwiftUI chat view, headless ChatEngine, LLM-agnostic via AnyLanguageModel. Read-only by default with configurable allowlists. Robust SQL parser with 63 tests. Includes demo app with GitHub stars dataset.
This commit is contained in:
164
Tests/SwiftDBAITests/ChatViewTests.swift
Normal file
164
Tests/SwiftDBAITests/ChatViewTests.swift
Normal file
@@ -0,0 +1,164 @@
|
||||
// ChatViewTests.swift
|
||||
// SwiftDBAITests
|
||||
//
|
||||
// Tests for ChatView, ChatViewModel, and MessageBubbleView integration
|
||||
// with ScrollableDataTableView.
|
||||
|
||||
import Testing
|
||||
import Foundation
|
||||
@testable import SwiftDBAI
|
||||
|
||||
@Suite("SchemaReadiness Tests")
|
||||
struct SchemaReadinessTests {
|
||||
|
||||
@Test("SchemaReadiness isReady returns true only for ready state")
|
||||
func isReadyProperty() {
|
||||
#expect(SchemaReadiness.idle.isReady == false)
|
||||
#expect(SchemaReadiness.loading.isReady == false)
|
||||
#expect(SchemaReadiness.ready(tableCount: 3).isReady == true)
|
||||
#expect(SchemaReadiness.failed("error").isReady == false)
|
||||
}
|
||||
}
|
||||
|
||||
@Suite("ChatViewModel Tests")
|
||||
struct ChatViewModelTests {
|
||||
|
||||
@Test("Messages with query results produce DataTable-compatible data")
|
||||
func messageWithQueryResultHasTableData() {
|
||||
// A ChatMessage with a queryResult should have the data needed
|
||||
// for ScrollableDataTableView rendering
|
||||
let result = QueryResult(
|
||||
columns: ["id", "name", "score"],
|
||||
rows: [
|
||||
["id": .integer(1), "name": .text("Alice"), "score": .real(95.5)],
|
||||
["id": .integer(2), "name": .text("Bob"), "score": .real(87.3)],
|
||||
],
|
||||
sql: "SELECT id, name, score FROM users",
|
||||
executionTime: 0.01
|
||||
)
|
||||
|
||||
let message = ChatMessage(
|
||||
role: .assistant,
|
||||
content: "Found 2 users.",
|
||||
queryResult: result,
|
||||
sql: "SELECT id, name, score FROM users"
|
||||
)
|
||||
|
||||
// Verify queryResult is present and can be converted to DataTable
|
||||
#expect(message.queryResult != nil)
|
||||
#expect(message.queryResult!.columns.count == 3)
|
||||
#expect(message.queryResult!.rows.count == 2)
|
||||
|
||||
// Verify DataTable conversion works (this is what MessageBubbleView does)
|
||||
let dataTable = DataTable(message.queryResult!)
|
||||
#expect(dataTable.columnCount == 3)
|
||||
#expect(dataTable.rowCount == 2)
|
||||
#expect(dataTable.columns[0].name == "id")
|
||||
#expect(dataTable.columns[1].name == "name")
|
||||
#expect(dataTable.columns[2].name == "score")
|
||||
}
|
||||
|
||||
@Test("Messages without query results do not trigger table rendering")
|
||||
func messageWithoutQueryResult() {
|
||||
let message = ChatMessage(
|
||||
role: .assistant,
|
||||
content: "Hello! How can I help?",
|
||||
queryResult: nil,
|
||||
sql: nil
|
||||
)
|
||||
|
||||
#expect(message.queryResult == nil)
|
||||
}
|
||||
|
||||
@Test("Empty query results do not trigger table rendering")
|
||||
func emptyQueryResult() {
|
||||
let result = QueryResult(
|
||||
columns: [],
|
||||
rows: [],
|
||||
sql: "SELECT * FROM empty_table",
|
||||
executionTime: 0.001
|
||||
)
|
||||
|
||||
let message = ChatMessage(
|
||||
role: .assistant,
|
||||
content: "No results found.",
|
||||
queryResult: result,
|
||||
sql: "SELECT * FROM empty_table"
|
||||
)
|
||||
|
||||
// Even though queryResult exists, it has no columns/rows
|
||||
// MessageBubbleView checks both conditions before showing the table
|
||||
#expect(message.queryResult != nil)
|
||||
#expect(message.queryResult!.columns.isEmpty)
|
||||
#expect(message.queryResult!.rows.isEmpty)
|
||||
}
|
||||
|
||||
@Test("Mutation results do not trigger table rendering")
|
||||
func mutationQueryResult() {
|
||||
let result = QueryResult(
|
||||
columns: [],
|
||||
rows: [],
|
||||
sql: "INSERT INTO users (name) VALUES ('Charlie')",
|
||||
executionTime: 0.005,
|
||||
rowsAffected: 1
|
||||
)
|
||||
|
||||
let message = ChatMessage(
|
||||
role: .assistant,
|
||||
content: "Successfully inserted 1 row.",
|
||||
queryResult: result,
|
||||
sql: "INSERT INTO users (name) VALUES ('Charlie')"
|
||||
)
|
||||
|
||||
// Mutation results have empty columns — no table shown
|
||||
#expect(message.queryResult!.columns.isEmpty)
|
||||
}
|
||||
|
||||
@Test("Error messages never have query results")
|
||||
func errorMessageHasNoQueryResult() {
|
||||
let message = ChatMessage(
|
||||
role: .error,
|
||||
content: "SELECT operations are not allowed."
|
||||
)
|
||||
|
||||
#expect(message.queryResult == nil)
|
||||
#expect(message.role == .error)
|
||||
}
|
||||
|
||||
@Test("DataTable preserves column order from QueryResult")
|
||||
func dataTableColumnOrder() {
|
||||
let result = QueryResult(
|
||||
columns: ["date", "revenue", "category"],
|
||||
rows: [
|
||||
["date": .text("2024-01-01"), "revenue": .real(1500.0), "category": .text("Electronics")],
|
||||
],
|
||||
sql: "SELECT date, revenue, category FROM sales",
|
||||
executionTime: 0.02
|
||||
)
|
||||
|
||||
let dataTable = DataTable(result)
|
||||
#expect(dataTable.columnNames == ["date", "revenue", "category"])
|
||||
}
|
||||
|
||||
@Test("Large result sets are renderable as DataTable")
|
||||
func largeResultSet() {
|
||||
var rows: [[String: QueryResult.Value]] = []
|
||||
for i in 0..<500 {
|
||||
rows.append([
|
||||
"id": .integer(Int64(i)),
|
||||
"value": .real(Double(i) * 1.5),
|
||||
])
|
||||
}
|
||||
|
||||
let result = QueryResult(
|
||||
columns: ["id", "value"],
|
||||
rows: rows,
|
||||
sql: "SELECT id, value FROM big_table",
|
||||
executionTime: 0.15
|
||||
)
|
||||
|
||||
let dataTable = DataTable(result)
|
||||
#expect(dataTable.rowCount == 500)
|
||||
#expect(dataTable.columnCount == 2)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user