109 lines
4.2 KiB
OCaml
109 lines
4.2 KiB
OCaml
|
|
(* Copyright (C) 2022--2025 Petter A. Urkedal <paurkedal@gmail.com>
|
||
|
|
*
|
||
|
|
* This library is free software; you can redistribute it and/or modify it
|
||
|
|
* under the terms of the GNU Lesser General Public License as published by
|
||
|
|
* the Free Software Foundation, either version 3 of the License, or (at your
|
||
|
|
* option) any later version, with the LGPL-3.0 Linking Exception.
|
||
|
|
*
|
||
|
|
* This library is distributed in the hope that it will be useful, but WITHOUT
|
||
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||
|
|
* License for more details.
|
||
|
|
*
|
||
|
|
* You should have received a copy of the GNU Lesser General Public License
|
||
|
|
* and the LGPL-3.0 Linking Exception along with this library. If not, see
|
||
|
|
* <http://www.gnu.org/licenses/> and <https://spdx.org>, respectively.
|
||
|
|
*)
|
||
|
|
|
||
|
|
module Make (Ground : Testlib.Sig.Ground) = struct
|
||
|
|
open Ground
|
||
|
|
open Ground.Fiber.Infix
|
||
|
|
|
||
|
|
let env driver_info = function
|
||
|
|
| "engine_innodb" ->
|
||
|
|
(match Caqti_driver_info.dialect_tag driver_info with
|
||
|
|
| `Mysql -> Caqti_query.L" ENGINE = InnoDB"
|
||
|
|
| _ -> Caqti_query.S[])
|
||
|
|
| _ -> raise Not_found
|
||
|
|
|
||
|
|
let create_reqs =
|
||
|
|
List.map Caqti_template.Create.(direct T.(unit -->. unit)) [
|
||
|
|
"DROP TABLE IF EXISTS caqti_test_publication";
|
||
|
|
"DROP TABLE IF EXISTS caqti_test_genre";
|
||
|
|
"CREATE TABLE caqti_test_genre \
|
||
|
|
(id INTEGER PRIMARY KEY, \
|
||
|
|
genre VARCHAR(40) UNIQUE NOT NULL CHECK (genre != ''))\
|
||
|
|
$(engine_innodb)";
|
||
|
|
"CREATE TABLE caqti_test_publication \
|
||
|
|
(title VARCHAR(160) UNIQUE NOT NULL, \
|
||
|
|
author TEXT NOT NULL, \
|
||
|
|
genre_id INTEGER NOT NULL, \
|
||
|
|
FOREIGN KEY (genre_id) REFERENCES caqti_test_genre (id))\
|
||
|
|
$(engine_innodb)";
|
||
|
|
"INSERT INTO caqti_test_genre VALUES (1, 'fiction'), (2, 'nonfiction')";
|
||
|
|
"INSERT INTO caqti_test_publication VALUES ('Fiction', 'N.N.', 2)"
|
||
|
|
]
|
||
|
|
|
||
|
|
let not_null_violation_req =
|
||
|
|
Caqti_template.Create.(static T.(unit -->. unit))
|
||
|
|
"INSERT INTO caqti_test_genre VALUES (NULL, NULL)"
|
||
|
|
|
||
|
|
let unique_violation_req =
|
||
|
|
Caqti_template.Create.(static T.(unit -->. unit))
|
||
|
|
"INSERT INTO caqti_test_genre VALUES (3, 'fiction')"
|
||
|
|
|
||
|
|
let foreign_key_violation_req =
|
||
|
|
Caqti_template.Create.(static T.(unit -->. unit))
|
||
|
|
"INSERT INTO caqti_test_publication \
|
||
|
|
VALUES ('Unclassified', 'N.N.', 0)"
|
||
|
|
|
||
|
|
let check_violation_req =
|
||
|
|
Caqti_template.Create.(static T.(unit -->. unit))
|
||
|
|
"INSERT INTO caqti_test_genre VALUES (3, '')"
|
||
|
|
|
||
|
|
(* Missing:
|
||
|
|
* - Restrict violation is only mapped for PostgreSQL, but even there it is
|
||
|
|
* likely unused (deleting a restricted FK casuse FK violation.
|
||
|
|
* - Exclusion violation is also only mapped for PostgreSQL. *)
|
||
|
|
let check_restrict_violation_req =
|
||
|
|
Caqti_template.Create.(static T.(unit -->. unit))
|
||
|
|
"DELETE FROM caqti_test_genre WHERE id = 2"
|
||
|
|
|
||
|
|
(* TODO: exclusion *)
|
||
|
|
|
||
|
|
let harness (module Db : CONNECTION) =
|
||
|
|
List_result_fiber.iter_s (fun req -> Db.exec req ()) create_reqs
|
||
|
|
>>= or_fail
|
||
|
|
|
||
|
|
let make_test_case (expected_cause, req) =
|
||
|
|
let test (module Db : CONNECTION) =
|
||
|
|
Db.exec req () >|= function
|
||
|
|
| Ok () -> Alcotest.fail "Error not reported."
|
||
|
|
| Error (`Request_failed _ | `Response_failed _ as err) ->
|
||
|
|
let actual_cause = Caqti_error.cause err in
|
||
|
|
|
||
|
|
(* Skip for sqlite3 < 5.2.0 due to missing extended error code. *)
|
||
|
|
let open Caqti_template.Version.Infix in
|
||
|
|
(match Db.dialect with
|
||
|
|
| Caqti_template.Dialect.Sqlite {server_version; _} ->
|
||
|
|
if server_version <=* "5.2" then Alcotest.skip ()
|
||
|
|
| _ -> ());
|
||
|
|
|
||
|
|
Alcotest.(check string) "cause"
|
||
|
|
(Caqti_error.show_cause expected_cause)
|
||
|
|
(Caqti_error.show_cause actual_cause)
|
||
|
|
| Error err ->
|
||
|
|
Alcotest.failf "Unexpected error: %a" Caqti_error.pp err
|
||
|
|
in
|
||
|
|
(Caqti_error.show_cause expected_cause, `Quick, test)
|
||
|
|
|
||
|
|
let test_cases =
|
||
|
|
("harness", `Quick, harness) ::
|
||
|
|
List.map make_test_case [
|
||
|
|
`Not_null_violation, not_null_violation_req;
|
||
|
|
`Unique_violation, unique_violation_req;
|
||
|
|
`Foreign_key_violation, foreign_key_violation_req;
|
||
|
|
`Check_violation, check_violation_req;
|
||
|
|
]
|
||
|
|
end
|