|
| 1 | +defmodule Ecto.Integration.ConstraintsTest do |
| 2 | + use ExUnit.Case, async: true |
| 3 | + |
| 4 | + import Ecto.Migrator, only: [up: 4] |
| 5 | + alias Ecto.Integration.PoolRepo |
| 6 | + |
| 7 | + defmodule ConstraintMigration do |
| 8 | + use Ecto.Migration |
| 9 | + |
| 10 | + @table table(:constraints_test) |
| 11 | + |
| 12 | + def change do |
| 13 | + create @table do |
| 14 | + add :price, :integer |
| 15 | + add :from, :integer |
| 16 | + add :to, :integer |
| 17 | + end |
| 18 | + |
| 19 | + execute(&positive_price_up/0, &positive_price_down/0) |
| 20 | + end |
| 21 | + |
| 22 | + defp positive_price_up do |
| 23 | + # Only valid after MySQL 8.0.19 |
| 24 | + repo().query!("ALTER TABLE #{@table.name} ADD CONSTRAINT positive_price CHECK (price > 0);", [], [log: :info]) |
| 25 | + end |
| 26 | + |
| 27 | + defp positive_price_down do |
| 28 | + # Only valid after MySQL 8.0.19 |
| 29 | + repo().query!("ALTER TABLE #{@table.name} DROP CONSTRAINT positive_price;", [], [log: :info]) |
| 30 | + end |
| 31 | + end |
| 32 | + |
| 33 | + defmodule Constraint do |
| 34 | + use Ecto.Integration.Schema |
| 35 | + |
| 36 | + schema "constraints_test" do |
| 37 | + field :price, :integer |
| 38 | + field :from, :integer |
| 39 | + field :to, :integer |
| 40 | + end |
| 41 | + end |
| 42 | + |
| 43 | + @base_migration 2_000_000 |
| 44 | + |
| 45 | + setup_all do |
| 46 | + ExUnit.CaptureLog.capture_log(fn -> |
| 47 | + num = @base_migration + System.unique_integer([:positive]) |
| 48 | + up(PoolRepo, num, ConstraintMigration, log: false) |
| 49 | + end) |
| 50 | + |
| 51 | + :ok |
| 52 | + end |
| 53 | + |
| 54 | + test "check constraint" do |
| 55 | + # When the changeset doesn't expect the db error |
| 56 | + changeset = Ecto.Changeset.change(%Constraint{}, price: -10) |
| 57 | + exception = |
| 58 | + assert_raise Ecto.ConstraintError, ~r/constraint error when attempting to insert struct/, fn -> |
| 59 | + PoolRepo.insert(changeset) |
| 60 | + end |
| 61 | + |
| 62 | + assert exception.message =~ "\"positive_price\" (check_constraint)" |
| 63 | + assert exception.message =~ "The changeset has not defined any constraint." |
| 64 | + assert exception.message =~ "call `check_constraint/3`" |
| 65 | + |
| 66 | + # When the changeset does expect the db error, but doesn't give a custom message |
| 67 | + {:error, changeset} = |
| 68 | + changeset |
| 69 | + |> Ecto.Changeset.check_constraint(:price, name: :positive_price) |
| 70 | + |> PoolRepo.insert() |
| 71 | + assert changeset.errors == [price: {"is invalid", [constraint: :check, constraint_name: "positive_price"]}] |
| 72 | + assert changeset.data.__meta__.state == :built |
| 73 | + |
| 74 | + # When the changeset does expect the db error and gives a custom message |
| 75 | + changeset = Ecto.Changeset.change(%Constraint{}, price: -10) |
| 76 | + {:error, changeset} = |
| 77 | + changeset |
| 78 | + |> Ecto.Changeset.check_constraint(:price, name: :positive_price, message: "price must be greater than 0") |
| 79 | + |> PoolRepo.insert() |
| 80 | + assert changeset.errors == [price: {"price must be greater than 0", [constraint: :check, constraint_name: "positive_price"]}] |
| 81 | + assert changeset.data.__meta__.state == :built |
| 82 | + |
| 83 | + # When the change does not violate the check constraint |
| 84 | + changeset = Ecto.Changeset.change(%Constraint{}, price: 10, from: 100, to: 200) |
| 85 | + {:ok, changeset} = |
| 86 | + changeset |
| 87 | + |> Ecto.Changeset.check_constraint(:price, name: :positive_price, message: "price must be greater than 0") |
| 88 | + |> PoolRepo.insert() |
| 89 | + assert is_integer(changeset.id) |
| 90 | + end |
| 91 | +end |
0 commit comments