Add CI/CD configuration and API documentation
This commit is contained in:
+318
@@ -0,0 +1,318 @@
|
||||
import XCTest
|
||||
import GRDB
|
||||
|
||||
class DatabaseRegionObservationTests: GRDBTestCase {
|
||||
// Test passes if it compiles.
|
||||
// See <https://github.com/groue/GRDB.swift/issues/1541>
|
||||
func testAnyDatabaseWriter(writer: any DatabaseWriter) throws {
|
||||
let observation = DatabaseRegionObservation(tracking: .fullDatabase)
|
||||
|
||||
_ = observation.start(in: writer, onError: { _ in }, onChange: { _ in })
|
||||
|
||||
if #available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) {
|
||||
_ = observation.publisher(in: writer)
|
||||
}
|
||||
}
|
||||
|
||||
func testDatabaseRegionObservation_FullDatabase() throws {
|
||||
let dbQueue = try makeDatabaseQueue()
|
||||
try dbQueue.write {
|
||||
try $0.execute(sql: "CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
|
||||
try $0.execute(sql: "CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
|
||||
}
|
||||
|
||||
let notificationExpectation = expectation(description: "notification")
|
||||
notificationExpectation.assertForOverFulfill = true
|
||||
notificationExpectation.expectedFulfillmentCount = 3
|
||||
|
||||
let observation = DatabaseRegionObservation(tracking: .fullDatabase)
|
||||
|
||||
var count = 0
|
||||
let cancellable = observation.start(
|
||||
in: dbQueue,
|
||||
onError: { XCTFail("Unexpected error: \($0)") },
|
||||
onChange: { db in
|
||||
count += 1
|
||||
notificationExpectation.fulfill()
|
||||
})
|
||||
|
||||
try withExtendedLifetime(cancellable) {
|
||||
try dbQueue.write { db in
|
||||
try db.execute(sql: "INSERT INTO t1 (id, name) VALUES (1, 'foo')")
|
||||
}
|
||||
try dbQueue.write { db in
|
||||
try db.execute(sql: "INSERT INTO t2 (id, name) VALUES (1, 'foo')")
|
||||
}
|
||||
try dbQueue.write { db in
|
||||
try db.execute(sql: "INSERT INTO t1 (id, name) VALUES (2, 'foo')")
|
||||
try db.execute(sql: "INSERT INTO t2 (id, name) VALUES (2, 'foo')")
|
||||
}
|
||||
waitForExpectations(timeout: 1, handler: nil)
|
||||
|
||||
XCTAssertEqual(count, 3)
|
||||
}
|
||||
}
|
||||
|
||||
func testDatabaseRegionObservation_ImmediateCancellation() throws {
|
||||
let dbQueue = try makeDatabaseQueue()
|
||||
try dbQueue.write {
|
||||
try $0.execute(sql: "CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
|
||||
}
|
||||
|
||||
let notificationExpectation = expectation(description: "notification")
|
||||
notificationExpectation.isInverted = true
|
||||
|
||||
let observation = DatabaseRegionObservation(tracking: .fullDatabase)
|
||||
|
||||
let cancellable = observation.start(
|
||||
in: dbQueue,
|
||||
onError: { XCTFail("Unexpected error: \($0)") },
|
||||
onChange: { db in
|
||||
notificationExpectation.fulfill()
|
||||
})
|
||||
cancellable.cancel()
|
||||
|
||||
try withExtendedLifetime(cancellable) {
|
||||
try dbQueue.write { db in
|
||||
try db.execute(sql: "INSERT INTO t (id, name) VALUES (1, 'foo')")
|
||||
}
|
||||
waitForExpectations(timeout: 0.1, handler: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func testDatabaseRegionObservationVariadic() throws {
|
||||
let dbQueue = try makeDatabaseQueue()
|
||||
try dbQueue.write {
|
||||
try $0.execute(sql: "CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
|
||||
try $0.execute(sql: "CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
|
||||
}
|
||||
|
||||
let notificationExpectation = expectation(description: "notification")
|
||||
notificationExpectation.assertForOverFulfill = true
|
||||
notificationExpectation.expectedFulfillmentCount = 3
|
||||
|
||||
let request1 = SQLRequest<Row>(sql: "SELECT * FROM t1 ORDER BY id")
|
||||
let request2 = SQLRequest<Row>(sql: "SELECT * FROM t2 ORDER BY id")
|
||||
|
||||
let observation = DatabaseRegionObservation(tracking: request1, request2)
|
||||
|
||||
var count = 0
|
||||
let cancellable = observation.start(
|
||||
in: dbQueue,
|
||||
onError: { XCTFail("Unexpected error: \($0)") },
|
||||
onChange: { db in
|
||||
count += 1
|
||||
notificationExpectation.fulfill()
|
||||
})
|
||||
|
||||
try withExtendedLifetime(cancellable) {
|
||||
try dbQueue.write { db in
|
||||
try db.execute(sql: "INSERT INTO t1 (id, name) VALUES (1, 'foo')")
|
||||
}
|
||||
try dbQueue.write { db in
|
||||
try db.execute(sql: "INSERT INTO t2 (id, name) VALUES (1, 'foo')")
|
||||
}
|
||||
try dbQueue.write { db in
|
||||
try db.execute(sql: "INSERT INTO t1 (id, name) VALUES (2, 'foo')")
|
||||
try db.execute(sql: "INSERT INTO t2 (id, name) VALUES (2, 'foo')")
|
||||
}
|
||||
waitForExpectations(timeout: 1, handler: nil)
|
||||
|
||||
XCTAssertEqual(count, 3)
|
||||
}
|
||||
}
|
||||
|
||||
func testDatabaseRegionObservationArray() throws {
|
||||
let dbQueue = try makeDatabaseQueue()
|
||||
try dbQueue.write {
|
||||
try $0.execute(sql: "CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
|
||||
try $0.execute(sql: "CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
|
||||
}
|
||||
|
||||
let notificationExpectation = expectation(description: "notification")
|
||||
notificationExpectation.assertForOverFulfill = true
|
||||
notificationExpectation.expectedFulfillmentCount = 3
|
||||
|
||||
let request1 = SQLRequest<Row>(sql: "SELECT * FROM t1 ORDER BY id")
|
||||
let request2 = SQLRequest<Row>(sql: "SELECT * FROM t2 ORDER BY id")
|
||||
|
||||
let observation = DatabaseRegionObservation(tracking: [request1, request2])
|
||||
|
||||
var count = 0
|
||||
let cancellable = observation.start(
|
||||
in: dbQueue,
|
||||
onError: { XCTFail("Unexpected error: \($0)") },
|
||||
onChange: { db in
|
||||
count += 1
|
||||
notificationExpectation.fulfill()
|
||||
})
|
||||
|
||||
try withExtendedLifetime(cancellable) {
|
||||
try dbQueue.write { db in
|
||||
try db.execute(sql: "INSERT INTO t1 (id, name) VALUES (1, 'foo')")
|
||||
}
|
||||
try dbQueue.write { db in
|
||||
try db.execute(sql: "INSERT INTO t2 (id, name) VALUES (1, 'foo')")
|
||||
}
|
||||
try dbQueue.write { db in
|
||||
try db.execute(sql: "INSERT INTO t1 (id, name) VALUES (2, 'foo')")
|
||||
try db.execute(sql: "INSERT INTO t2 (id, name) VALUES (2, 'foo')")
|
||||
}
|
||||
waitForExpectations(timeout: 1, handler: nil)
|
||||
|
||||
XCTAssertEqual(count, 3)
|
||||
}
|
||||
}
|
||||
|
||||
func testDatabaseRegionDefaultCancellation() throws {
|
||||
let dbQueue = try makeDatabaseQueue()
|
||||
try dbQueue.write { try $0.execute(sql: "CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)") }
|
||||
|
||||
let notificationExpectation = expectation(description: "notification")
|
||||
notificationExpectation.assertForOverFulfill = true
|
||||
notificationExpectation.expectedFulfillmentCount = 2
|
||||
|
||||
let observation = DatabaseRegionObservation(tracking: SQLRequest<Row>(sql: "SELECT * FROM t ORDER BY id"))
|
||||
|
||||
var count = 0
|
||||
do {
|
||||
let cancellable = observation.start(
|
||||
in: dbQueue,
|
||||
onError: { XCTFail("Unexpected error: \($0)") },
|
||||
onChange: { db in
|
||||
count += 1
|
||||
notificationExpectation.fulfill()
|
||||
})
|
||||
|
||||
try withExtendedLifetime(cancellable) {
|
||||
try dbQueue.write { db in
|
||||
try db.execute(sql: "INSERT INTO t (id, name) VALUES (1, 'foo')")
|
||||
}
|
||||
try dbQueue.write { db in
|
||||
try db.execute(sql: "INSERT INTO t (id, name) VALUES (2, 'bar')")
|
||||
}
|
||||
}
|
||||
}
|
||||
// not notified
|
||||
try dbQueue.write { db in
|
||||
try db.execute(sql: "INSERT INTO t (id, name) VALUES (3, 'baz')")
|
||||
}
|
||||
waitForExpectations(timeout: 1, handler: nil)
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testDatabaseRegionExtentNextTransaction() throws {
|
||||
let dbQueue = try makeDatabaseQueue()
|
||||
try dbQueue.write { try $0.execute(sql: "CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)") }
|
||||
|
||||
let notificationExpectation = expectation(description: "notification")
|
||||
notificationExpectation.assertForOverFulfill = true
|
||||
notificationExpectation.expectedFulfillmentCount = 1
|
||||
|
||||
let observation = DatabaseRegionObservation(tracking: SQLRequest<Row>(sql: "SELECT * FROM t ORDER BY id"))
|
||||
|
||||
var count = 0
|
||||
var cancellable: AnyDatabaseCancellable?
|
||||
cancellable = observation.start(
|
||||
in: dbQueue,
|
||||
onError: { XCTFail("Unexpected error: \($0)") },
|
||||
onChange: { db in
|
||||
cancellable?.cancel()
|
||||
count += 1
|
||||
notificationExpectation.fulfill()
|
||||
})
|
||||
|
||||
try withExtendedLifetime(cancellable) {
|
||||
try dbQueue.write { db in
|
||||
try db.execute(sql: "INSERT INTO t (id, name) VALUES (1, 'foo')")
|
||||
}
|
||||
// not notified
|
||||
try dbQueue.write { db in
|
||||
try db.execute(sql: "INSERT INTO t (id, name) VALUES (2, 'bar')")
|
||||
}
|
||||
waitForExpectations(timeout: 1, handler: nil)
|
||||
|
||||
XCTAssertEqual(count, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func test_DatabaseRegionObservation_is_triggered_by_explicit_change_notification() throws {
|
||||
let dbQueue1 = try makeDatabaseQueue(filename: "test.sqlite")
|
||||
try dbQueue1.write { db in
|
||||
try db.execute(sql: "CREATE TABLE test(a)")
|
||||
}
|
||||
|
||||
let undetectedExpectation = expectation(description: "undetected")
|
||||
undetectedExpectation.isInverted = true
|
||||
|
||||
let detectedExpectation = expectation(description: "detected")
|
||||
|
||||
let observation = DatabaseRegionObservation(tracking: Table("test"))
|
||||
let cancellable = observation.start(
|
||||
in: dbQueue1,
|
||||
onError: { error in XCTFail("Unexpected error: \(error)") },
|
||||
onChange: { _ in
|
||||
undetectedExpectation.fulfill()
|
||||
detectedExpectation.fulfill()
|
||||
})
|
||||
|
||||
try withExtendedLifetime(cancellable) {
|
||||
// Change performed from external connection is not detected...
|
||||
let dbQueue2 = try makeDatabaseQueue(filename: "test.sqlite")
|
||||
try dbQueue2.write { db in
|
||||
try db.execute(sql: "INSERT INTO test (a) VALUES (1)")
|
||||
}
|
||||
wait(for: [undetectedExpectation], timeout: 2)
|
||||
|
||||
// ... until we perform an explicit change notification
|
||||
try dbQueue1.write { db in
|
||||
try db.notifyChanges(in: Table("test"))
|
||||
}
|
||||
wait(for: [detectedExpectation], timeout: 2)
|
||||
}
|
||||
}
|
||||
|
||||
// Regression test for https://github.com/groue/GRDB.swift/issues/514
|
||||
// TODO: uncomment and make this test pass.
|
||||
// Well, actually, selecting only the rowid has SQLite authorizer advertise
|
||||
// that we select the whole table. This creates undesired database
|
||||
// observation notifications.
|
||||
// func testIssue514() throws {
|
||||
// let dbQueue = try makeDatabaseQueue()
|
||||
// try dbQueue.write { db in
|
||||
// try db.create(table: "gallery") { t in
|
||||
// t.primaryKey("id", .integer)
|
||||
// t.column("status", .integer)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// struct Gallery: TableRecord { }
|
||||
// let observation = DatabaseRegionObservation(tracking: Gallery.select(Column("id")))
|
||||
//
|
||||
// var notificationCount = 0
|
||||
// let cancellable = observation.start(
|
||||
// in: dbQueue,
|
||||
// onError: { XCTFail("Unexpected error: \($0)") },
|
||||
// onChange: { _ in
|
||||
// notificationCount += 1
|
||||
// })
|
||||
//
|
||||
// try withExtendedLifetime(cancellable) {
|
||||
// try dbQueue.write { db in
|
||||
// try db.execute(sql: "INSERT INTO gallery (id, status) VALUES (NULL, 0)")
|
||||
// }
|
||||
// XCTAssertEqual(notificationCount, 1)
|
||||
//
|
||||
// try dbQueue.write { db in
|
||||
// try db.execute(sql: "UPDATE gallery SET status = 1")
|
||||
// }
|
||||
// XCTAssertEqual(notificationCount, 1) // status is not observed
|
||||
//
|
||||
// try dbQueue.write { db in
|
||||
// try db.execute(sql: "DELETE FROM gallery")
|
||||
// }
|
||||
// XCTAssertEqual(notificationCount, 2)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
Reference in New Issue
Block a user