farmbot_os/farmbot_core/test/asset/criteria_retriever_test.exs

592 lines
14 KiB
Elixir

defmodule FarmbotCore.Asset.CriteriaRetrieverTest do
use ExUnit.Case, async: true
use Mimic
alias FarmbotCore.Asset.{
CriteriaRetriever,
Point,
PointGroup,
Repo
}
setup :verify_on_exit!
@fake_point_group %PointGroup{
criteria: %{
"day" => %{"op" => ">", "days_ago" => 4},
"string_eq" => %{
"openfarm_slug" => ["five", "nine"],
"meta.created_by" => ["plant-detection"]
},
"number_eq" => %{"radius" => [6, 10, 11]},
"number_lt" => %{"x" => 7},
"number_gt" => %{"z" => 8}
}
}
@simple_point_group %PointGroup{
point_ids: [],
sort_type: "xy_ascending",
criteria: %{
"day" => %{
"op" => "<",
"days_ago" => 0
},
"string_eq" => %{
"pointer_type" => ["Plant"]
},
"number_eq" => %{},
"number_lt" => %{},
"number_gt" => %{}
}
}
# Use this is a fake "Timex.now()" value when mocking.
@now ~U[2222-12-10 02:22:22.222222Z]
@five_days_ago ~U[2222-12-05 01:11:11.111111Z]
@n 1000
def rand, do: Enum.random(0..@n) / 1
def point!(%{id: id} = params) do
point =
Map.merge(
%Point{
id: id,
name: "point #{id}",
meta: %{},
plant_stage: "planted",
created_at: @now,
pointer_type: "Plant",
radius: 10.0,
tool_id: nil,
discarded_at: nil,
gantry_mounted: false,
x: 0.0,
y: 0.0,
z: 0.0
},
params
)
Repo.insert!(point)
point
end
def point_group_with_fake_points do
Repo.delete_all(PointGroup)
Repo.delete_all(Point)
whitelist = [
%Point{id: 1, x: rand(), y: rand(), z: rand()},
%Point{id: 2, x: rand(), y: rand(), z: rand()},
%Point{id: 3, x: rand(), y: rand(), z: rand()}
]
exclusion = [
%Point{created_at: @now},
%Point{openfarm_slug: "not five"},
%Point{radius: 10.0},
%Point{x: 10.0},
%Point{z: 6.0}
]
incusion = [
# INCLUSION POINTS
%Point{created_at: ~U[2222-12-04 02:22:22.222222Z]},
%Point{openfarm_slug: "five"},
%Point{radius: 6.0},
%Point{x: 7.0},
%Point{z: 8.0}
]
points = whitelist ++ exclusion ++ incusion
Enum.map(points, fn p -> Repo.insert!(p) end)
pg = %PointGroup{@fake_point_group | point_ids: [1, 2, 3]}
Repo.insert!(pg)
pg
end
test "direct match on `pointer_type` via `string_eq`" do
Repo.delete_all(PointGroup)
Repo.delete_all(Point)
point!(%{id: 1, pointer_type: "Plant"})
point!(%{id: 2, pointer_type: "Weed"})
point!(%{id: 3, pointer_type: "ToolSlot"})
point!(%{id: 4, pointer_type: "GenericPointer"})
result = CriteriaRetriever.run(@simple_point_group)
assert Enum.count(result) == 1
end
test "run/1" do
expect(Timex, :now, fn -> @now end)
pg = point_group_with_fake_points()
point!(%{
id: 888,
created_at: @five_days_ago,
openfarm_slug: "five",
meta: %{"created_by" => "not-plant-detection"},
radius: 10.0,
x: 6.0,
z: 9.0
})
perfect_match =
point!(%{
id: 999,
created_at: @five_days_ago,
openfarm_slug: "five",
meta: %{"created_by" => "plant-detection"},
radius: 10.0,
x: 6.0,
z: 9.0
})
expected = [perfect_match.id, 1, 2, 3]
results = Enum.map(CriteriaRetriever.run(pg), fn p -> p.id end)
assert Enum.count(expected) == Enum.count(results)
Enum.map(expected, fn id -> assert Enum.member?(results, id) end)
end
@tag :capture_log
test "point group that does not define criteria" do
Repo.delete_all(PointGroup)
Repo.delete_all(Point)
whitelist = [88457, 88455]
point!(%{
created_at: ~U[2020-01-09 19:09:39.176321Z],
discarded_at: nil,
gantry_mounted: false,
id: 88455,
meta: %{},
name: "Spinach",
openfarm_slug: nil,
plant_stage: "planned",
planted_at: nil,
pointer_type: "Plant",
radius: 25.0,
tool_id: nil,
updated_at: ~U[1970-11-07 16:52:31.618000Z],
x: 400.0,
y: 100.0,
z: 0.0
})
point!(%{
created_at: ~U[2020-01-09 19:09:39.413318Z],
discarded_at: nil,
gantry_mounted: false,
id: 88456,
meta: %{},
name: "Spinach",
openfarm_slug: nil,
plant_stage: "planned",
planted_at: nil,
pointer_type: "Plant",
radius: 25.0,
tool_id: nil,
updated_at: ~U[1970-11-07 16:52:31.618000Z],
x: 400.0,
y: 300.0,
z: 0.0
})
point!(%{
created_at: ~U[2020-01-09 19:09:39.610901Z],
discarded_at: nil,
gantry_mounted: false,
id: 88457,
meta: %{},
name: "Spinach",
openfarm_slug: nil,
plant_stage: "planned",
planted_at: nil,
pointer_type: "Plant",
radius: 25.0,
tool_id: nil,
updated_at: ~U[1970-11-07 16:52:31.618000Z],
x: 600.0,
y: 100.0,
z: 0.0
})
point!(%{
created_at: ~U[2020-01-09 19:09:39.824048Z],
discarded_at: nil,
gantry_mounted: false,
id: 88458,
meta: %{},
name: "Spinach",
openfarm_slug: nil,
plant_stage: "planned",
planted_at: nil,
pointer_type: "Plant",
radius: 25.0,
tool_id: nil,
updated_at: ~U[1970-11-07 16:52:31.618000Z],
x: 600.0,
y: 300.0,
z: 0.0
})
point!(%{
created_at: ~U[2020-01-09 19:09:40.012075Z],
discarded_at: nil,
gantry_mounted: false,
id: 88459,
meta: %{},
name: "Spinach",
openfarm_slug: nil,
plant_stage: "planned",
planted_at: nil,
pointer_type: "Plant",
radius: 25.0,
tool_id: nil,
updated_at: ~U[1970-11-07 16:52:31.618000Z],
x: 800.0,
y: 300.0,
z: 0.0
})
point!(%{
created_at: ~U[2020-01-09 19:09:40.202385Z],
discarded_at: nil,
gantry_mounted: false,
id: 88460,
meta: %{},
name: "Spinach",
openfarm_slug: nil,
plant_stage: "planned",
planted_at: nil,
pointer_type: "Plant",
radius: 25.0,
tool_id: nil,
updated_at: ~U[1970-11-07 16:52:31.618000Z],
x: 800.0,
y: 100.0,
z: 0.0
})
point!(%{
created_at: ~U[2020-01-09 19:09:40.402777Z],
discarded_at: nil,
gantry_mounted: false,
id: 88461,
meta: %{},
name: "Broccoli",
openfarm_slug: nil,
plant_stage: "planned",
planted_at: nil,
pointer_type: "Plant",
radius: 25.0,
tool_id: nil,
updated_at: ~U[1970-11-07 16:52:31.618000Z],
x: 600.0,
y: 700.0,
z: 0.0
})
point!(%{
created_at: ~U[2020-01-09 19:09:40.776337Z],
discarded_at: nil,
gantry_mounted: false,
id: 88462,
meta: %{},
name: "Beets",
openfarm_slug: nil,
plant_stage: "planned",
planted_at: nil,
pointer_type: "Plant",
radius: 25.0,
tool_id: nil,
updated_at: ~U[1970-11-07 16:52:31.618000Z],
x: 800.0,
y: 1.1e3,
z: 0.0
})
point!(%{
created_at: ~U[2020-01-09 19:09:40.960424Z],
discarded_at: nil,
gantry_mounted: false,
id: 88463,
meta: %{},
name: "Beets",
openfarm_slug: nil,
plant_stage: "planned",
planted_at: nil,
pointer_type: "Plant",
radius: 25.0,
tool_id: nil,
updated_at: ~U[1970-11-07 16:52:31.618000Z],
x: 600.0,
y: 1.1e3,
z: 0.0
})
point!(%{
created_at: ~U[2020-01-09 19:09:41.171967Z],
discarded_at: nil,
gantry_mounted: false,
id: 88464,
meta: %{},
name: "Beets",
openfarm_slug: nil,
plant_stage: "planned",
planted_at: nil,
pointer_type: "Plant",
radius: 25.0,
tool_id: nil,
updated_at: ~U[1970-11-07 16:52:31.618000Z],
x: 400.0,
y: 1.1e3,
z: 0.0
})
point!(%{
created_at: ~U[2020-01-09 19:09:41.393021Z],
discarded_at: nil,
gantry_mounted: false,
id: 88465,
meta: %{},
name: "Beets",
openfarm_slug: nil,
plant_stage: "planned",
planted_at: nil,
pointer_type: "Plant",
radius: 25.0,
tool_id: nil,
updated_at: ~U[1970-11-07 16:52:31.618000Z],
x: 200.0,
y: 1.1e3,
z: 0.0
})
point!(%{
created_at: ~U[2020-02-21 01:41:07.714000Z],
discarded_at: nil,
gantry_mounted: true,
id: 88798,
meta: %{},
name: "Tool Slot",
openfarm_slug: nil,
plant_stage: nil,
planted_at: nil,
pointer_type: "ToolSlot",
radius: nil,
tool_id: nil,
updated_at: ~U[1970-11-07 16:52:31.618000Z],
x: 0.0,
y: 0.0,
z: -20.0
})
point!(%{
created_at: ~U[2020-02-21 18:30:56.301000Z],
discarded_at: nil,
gantry_mounted: false,
id: 88805,
meta: %{},
name: "Slot",
openfarm_slug: nil,
plant_stage: nil,
planted_at: nil,
pointer_type: "ToolSlot",
radius: nil,
tool_id: 12143,
updated_at: ~U[1970-11-07 16:52:31.618000Z],
x: 200.0,
y: 200.0,
z: 0.0
})
point!(%{
created_at: ~U[2020-02-21 18:47:45.170000Z],
discarded_at: nil,
gantry_mounted: false,
id: 88806,
meta: %{},
name: "Slot",
openfarm_slug: nil,
plant_stage: nil,
planted_at: nil,
pointer_type: "ToolSlot",
radius: nil,
tool_id: nil,
updated_at: ~U[1970-11-07 16:52:31.618000Z],
x: 0.0,
y: 0.0,
z: 0.0
})
point!(%{
created_at: ~U[2020-02-21 20:01:59.960000Z],
discarded_at: nil,
gantry_mounted: false,
id: 88828,
meta: %{
"color" => "green",
"created_by" => "farm-designer",
"type" => "point"
},
name: "Created Point",
openfarm_slug: nil,
plant_stage: nil,
planted_at: nil,
pointer_type: "GenericPointer",
radius: 15.0,
tool_id: nil,
updated_at: ~U[1970-11-07 16:52:31.618000Z],
x: 1.0,
y: 1.0,
z: 0.0
})
point!(%{
created_at: ~U[2020-02-29 21:08:40.934000Z],
discarded_at: nil,
gantry_mounted: false,
id: 88887,
meta: %{
"color" => "red",
"created_by" => "farm-designer",
"type" => "weed"
},
name: "Created Weed",
openfarm_slug: nil,
plant_stage: nil,
planted_at: nil,
pointer_type: "GenericPointer",
radius: 15.0,
tool_id: nil,
updated_at: ~U[2020-02-29 21:08:40.934000Z],
x: 100.0,
y: 100.0,
z: 0.0
})
pg = %PointGroup{
created_at: ~U[2020-02-29 21:14:33.337000Z],
criteria: %{
"day" => %{"days_ago" => 0, "op" => "<"},
"number_eq" => %{},
"number_gt" => %{},
"number_lt" => %{},
"string_eq" => %{}
},
id: 201,
name: "Test (Broke?)",
point_ids: whitelist,
sort_type: "xy_ascending",
updated_at: ~U[2020-03-02 21:55:26.973000Z]
}
Repo.insert!(pg)
results = Enum.map(CriteriaRetriever.run(pg), fn p -> p.id end)
assert Enum.count(whitelist) == Enum.count(results)
Enum.map(whitelist, fn id -> assert Enum.member?(results, id) end)
end
test "edge case: Filter by crop type" do
Repo.delete_all(PointGroup)
Repo.delete_all(Point)
ok = point!(%{id: 1, pointer_type: "Plant", openfarm_slug: "spinach"})
point!(%{id: 2, pointer_type: "Plant", openfarm_slug: "beetroot"})
point!(%{id: 3, pointer_type: "Weed", openfarm_slug: "thistle"})
point!(%{id: 4, pointer_type: "Weed", openfarm_slug: "spinach"})
pg = %PointGroup{
:id => 241,
:point_ids => [],
:criteria => %{
"day" => %{
"op" => "<",
"days_ago" => 0
},
"string_eq" => %{
"pointer_type" => ["Plant"],
"openfarm_slug" => ["spinach"]
},
"number_eq" => %{},
"number_lt" => %{},
"number_gt" => %{}
}
}
ids = CriteriaRetriever.run(pg) |> Enum.map(fn p -> p.id end)
assert Enum.member?(ids, ok.id)
assert Enum.count(ids) == 1
end
test "edge case: Retrieves by `day` criteria only" do
Repo.delete_all(PointGroup)
Repo.delete_all(Point)
days_ago4 = Timex.shift(@now, days: -4)
days_ago2 = Timex.shift(@now, days: -2)
expect(Timex, :now, fn -> @now end)
point!(%{id: 1, pointer_type: "Plant", created_at: days_ago4})
p2 = point!(%{id: 2, pointer_type: "Plant", created_at: days_ago2})
pg1 = %PointGroup{
id: 212,
created_at: Timex.shift(@now, hours: -1),
updated_at: Timex.shift(@now, hours: -1),
name: "Less than 2 days ago",
point_ids: [],
sort_type: "yx_descending",
criteria: %{
day: %{"op" => "<", "days_ago" => 3},
string_eq: %{},
number_eq: %{},
number_lt: %{},
number_gt: %{}
}
}
ids = CriteriaRetriever.run(pg1) |> Enum.map(fn p -> p.id end)
assert Enum.count(ids) == 1
assert Enum.member?(ids, p2.id)
end
test "edge case: Filter by slot direction" do
Repo.delete_all(PointGroup)
Repo.delete_all(Point)
ok = point!(%{id: 1, pointer_type: "ToolSlot", pullout_direction: 3})
point!(%{id: 2, pointer_type: "Weed", pullout_direction: 3})
point!(%{id: 3, pointer_type: "ToolSlot", pullout_direction: 4})
point!(%{id: 4, pointer_type: "GenericPointer", pullout_direction: 2})
pg = %PointGroup{
:id => 242,
:name => "Filter by slot direction",
:point_ids => [],
:sort_type => "xy_ascending",
:criteria => %{
"day" => %{
"op" => "<",
"days_ago" => 0
},
"string_eq" => %{
"pointer_type" => ["ToolSlot"]
},
"number_eq" => %{
"pullout_direction" => [3]
},
"number_lt" => %{},
"number_gt" => %{}
}
}
ids = CriteriaRetriever.run(pg) |> Enum.map(fn p -> p.id end)
assert Enum.member?(ids, ok.id)
assert Enum.count(ids) == 1
end
end