Prepare stable Google Play review fixtures
This commit is contained in:
parent
0965523dc1
commit
4d321b92f5
|
|
@ -44,3 +44,18 @@ access field:
|
||||||
- Confirm all data is synthetic and no real user can be messaged or located.
|
- Confirm all data is synthetic and no real user can be messaged or located.
|
||||||
- Confirm the password works from a clean Play-delivered install without a
|
- Confirm the password works from a clean Play-delivered install without a
|
||||||
second factor, one-time code, developer browser session, or location gate.
|
second factor, one-time code, developer browser session, or location gate.
|
||||||
|
|
||||||
|
After both dedicated accounts have registered, confirmed their email, and set
|
||||||
|
their fixed passwords through the production UI, an operator can create the
|
||||||
|
stable synthetic records from the production checkout:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/prepare-play-review.sh \
|
||||||
|
REVIEWER_EMAIL COUNTERPART_EMAIL \
|
||||||
|
--confirm whoneedhelp.com \
|
||||||
|
/srv/who_need_help-production/.env
|
||||||
|
```
|
||||||
|
|
||||||
|
The command does not create or change credentials. It refuses missing,
|
||||||
|
unconfirmed, suspended, passwordless, staff, or duplicate accounts and is
|
||||||
|
idempotent for its one request and one activity.
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,9 @@ require Play Console. It contains no account credentials or signing keys.
|
||||||
- Create a dedicated non-staff production reviewer account with a fixed,
|
- Create a dedicated non-staff production reviewer account with a fixed,
|
||||||
reusable password. Put its credentials only in Play Console App access and
|
reusable password. Put its credentials only in Play Console App access and
|
||||||
the operator-controlled password manager.
|
the operator-controlled password manager.
|
||||||
- Create stable synthetic `Play review` request and activity records with a
|
- Register and confirm two dedicated non-staff accounts with fixed passwords,
|
||||||
second synthetic counterpart, then verify every reviewer instruction from a
|
then create their stable synthetic `Play review` request and activity using
|
||||||
|
`scripts/prepare-play-review.sh`. Verify every reviewer instruction from a
|
||||||
clean installation.
|
clean installation.
|
||||||
- Complete App content: App access, Ads, Content rating, Target audience,
|
- Complete App content: App access, Ads, Content rating, Target audience,
|
||||||
News-app declaration, Data Safety, background-location declaration if Play
|
News-app declaration, Data Safety, background-location declaration if Play
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,251 @@ defmodule WhoNeedHelp.Release do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@play_review_request_title "Play review — medicine pickup"
|
||||||
|
@play_review_activity_title "Play review — public cinema meetup"
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Prepares stable, synthetic records for Google Play review using two existing
|
||||||
|
ordinary accounts.
|
||||||
|
|
||||||
|
Both accounts must already be confirmed, active, have accepted the terms,
|
||||||
|
have a password, and have no staff roles. The function never creates or
|
||||||
|
changes credentials. Repeating it returns the same request and activity.
|
||||||
|
"""
|
||||||
|
def prepare_play_review(reviewer_email, counterpart_email)
|
||||||
|
when is_binary(reviewer_email) and is_binary(counterpart_email) do
|
||||||
|
load_app()
|
||||||
|
|
||||||
|
with {:ok, reviewer} <- play_review_user(reviewer_email, :reviewer),
|
||||||
|
{:ok, counterpart} <- play_review_user(counterpart_email, :counterpart),
|
||||||
|
:ok <- different_play_review_users(reviewer, counterpart),
|
||||||
|
{:ok, medicine_category} <- active_category("medicine-pickup", :help),
|
||||||
|
{:ok, cinema_category} <- active_category("cinema-meetup", :activity),
|
||||||
|
{:ok, request} <-
|
||||||
|
ensure_play_review_request(reviewer, counterpart, medicine_category),
|
||||||
|
{:ok, activity} <-
|
||||||
|
ensure_play_review_activity(reviewer, counterpart, cinema_category) do
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
reviewer_id: reviewer.id,
|
||||||
|
counterpart_id: counterpart.id,
|
||||||
|
request_id: request.id,
|
||||||
|
request_path: "/requests/#{request.id}",
|
||||||
|
activity_id: activity.id,
|
||||||
|
activity_path: "/activities/#{activity.id}"
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prepare_play_review(_reviewer_email, _counterpart_email),
|
||||||
|
do: {:error, :invalid_email}
|
||||||
|
|
||||||
|
defp play_review_user(email, role) do
|
||||||
|
email = email |> String.trim() |> String.downcase()
|
||||||
|
|
||||||
|
case WhoNeedHelp.Repo.get_by(WhoNeedHelp.Accounts.User, email: email) do
|
||||||
|
nil ->
|
||||||
|
{:error, {role, :user_not_found}}
|
||||||
|
|
||||||
|
user ->
|
||||||
|
cond do
|
||||||
|
user.moderation_status != :active ->
|
||||||
|
{:error, {role, :account_not_active}}
|
||||||
|
|
||||||
|
is_nil(user.confirmed_at) ->
|
||||||
|
{:error, {role, :email_not_confirmed}}
|
||||||
|
|
||||||
|
is_nil(user.accepted_terms_at) ->
|
||||||
|
{:error, {role, :terms_not_accepted}}
|
||||||
|
|
||||||
|
not (is_binary(user.hashed_password) and byte_size(user.hashed_password) > 0) ->
|
||||||
|
{:error, {role, :password_not_set}}
|
||||||
|
|
||||||
|
WhoNeedHelp.Accounts.staff_roles(user) != [] ->
|
||||||
|
{:error, {role, :staff_account_forbidden}}
|
||||||
|
|
||||||
|
true ->
|
||||||
|
{:ok, user}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp different_play_review_users(%{id: id}, %{id: id}),
|
||||||
|
do: {:error, :accounts_must_be_distinct}
|
||||||
|
|
||||||
|
defp different_play_review_users(_reviewer, _counterpart), do: :ok
|
||||||
|
|
||||||
|
defp active_category(slug, mode) do
|
||||||
|
case WhoNeedHelp.Repo.get_by(WhoNeedHelp.Catalog.Category,
|
||||||
|
slug: slug,
|
||||||
|
mode: mode,
|
||||||
|
active: true
|
||||||
|
) do
|
||||||
|
nil -> {:error, {:category_not_found, slug}}
|
||||||
|
category -> {:ok, category}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp ensure_play_review_request(reviewer, counterpart, category) do
|
||||||
|
existing =
|
||||||
|
WhoNeedHelp.Help.HelpRequest
|
||||||
|
|> where(
|
||||||
|
[request],
|
||||||
|
request.requester_id == ^reviewer.id and
|
||||||
|
request.title == ^@play_review_request_title and
|
||||||
|
is_nil(request.hidden_at)
|
||||||
|
)
|
||||||
|
|> order_by([request], desc: request.inserted_at)
|
||||||
|
|> limit(1)
|
||||||
|
|> WhoNeedHelp.Repo.one()
|
||||||
|
|
||||||
|
with {:ok, request} <- reusable_or_create_request(existing, reviewer, category),
|
||||||
|
{:ok, _assignment} <- ensure_play_review_assignment(request, counterpart) do
|
||||||
|
{:ok, WhoNeedHelp.Repo.get!(WhoNeedHelp.Help.HelpRequest, request.id)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp reusable_or_create_request(
|
||||||
|
%{status: status} = request,
|
||||||
|
_reviewer,
|
||||||
|
_category
|
||||||
|
)
|
||||||
|
when status in [:open, :matched, :in_progress],
|
||||||
|
do: {:ok, request}
|
||||||
|
|
||||||
|
defp reusable_or_create_request(nil, reviewer, category) do
|
||||||
|
WhoNeedHelp.Help.create_request(WhoNeedHelp.Accounts.Scope.for_user(reviewer), %{
|
||||||
|
"title" => @play_review_request_title,
|
||||||
|
"description" =>
|
||||||
|
"Synthetic Google Play review flow. Coordinate only with the assigned review counterpart.",
|
||||||
|
"pickup_instructions" =>
|
||||||
|
"Synthetic review data only; no medicine, payment, or real-world handover is required.",
|
||||||
|
"structured_data" => %{"pickup_status" => "already_paid"},
|
||||||
|
"location_label" => "Kyiv public review area — synthetic data",
|
||||||
|
"latitude" => 50.4501,
|
||||||
|
"longitude" => 30.5234,
|
||||||
|
"location_radius_meters" => 1_000,
|
||||||
|
"location_visibility" => "approximate_public",
|
||||||
|
"urgency" => "scheduled",
|
||||||
|
"expires_at" => DateTime.add(DateTime.utc_now(:second), 120, :day),
|
||||||
|
"category_id" => category.id,
|
||||||
|
"safety_confirmed" => true
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp reusable_or_create_request(_finished_request, _reviewer, _category),
|
||||||
|
do: {:error, :review_request_not_reusable}
|
||||||
|
|
||||||
|
defp ensure_play_review_assignment(request, counterpart) do
|
||||||
|
assignment =
|
||||||
|
WhoNeedHelp.Repo.get_by(WhoNeedHelp.Help.Assignment,
|
||||||
|
request_id: request.id,
|
||||||
|
active: true
|
||||||
|
)
|
||||||
|
|
||||||
|
case assignment do
|
||||||
|
nil when request.status == :open ->
|
||||||
|
WhoNeedHelp.Help.accept_request(
|
||||||
|
WhoNeedHelp.Accounts.Scope.for_user(counterpart),
|
||||||
|
request.id
|
||||||
|
)
|
||||||
|
|
||||||
|
%{helper_id: helper_id} = assignment when helper_id == counterpart.id ->
|
||||||
|
{:ok, assignment}
|
||||||
|
|
||||||
|
nil ->
|
||||||
|
{:error, :review_assignment_missing}
|
||||||
|
|
||||||
|
_other_assignment ->
|
||||||
|
{:error, :review_request_assigned_to_another_user}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp ensure_play_review_activity(reviewer, counterpart, category) do
|
||||||
|
existing =
|
||||||
|
WhoNeedHelp.Activities.Activity
|
||||||
|
|> where(
|
||||||
|
[activity],
|
||||||
|
activity.creator_id == ^reviewer.id and
|
||||||
|
activity.title == ^@play_review_activity_title and
|
||||||
|
is_nil(activity.hidden_at)
|
||||||
|
)
|
||||||
|
|> order_by([activity], desc: activity.inserted_at)
|
||||||
|
|> limit(1)
|
||||||
|
|> WhoNeedHelp.Repo.one()
|
||||||
|
|
||||||
|
with {:ok, activity} <- reusable_or_create_activity(existing, reviewer, category),
|
||||||
|
{:ok, participant} <- ensure_play_review_participant(activity, counterpart),
|
||||||
|
{:ok, _participant} <- approve_play_review_participant(activity, participant, reviewer) do
|
||||||
|
{:ok, WhoNeedHelp.Repo.get!(WhoNeedHelp.Activities.Activity, activity.id)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp reusable_or_create_activity(%{status: :open} = activity, _reviewer, _category),
|
||||||
|
do: {:ok, activity}
|
||||||
|
|
||||||
|
defp reusable_or_create_activity(nil, reviewer, category) do
|
||||||
|
now = DateTime.utc_now(:second)
|
||||||
|
|
||||||
|
WhoNeedHelp.Activities.create_activity(WhoNeedHelp.Accounts.Scope.for_user(reviewer), %{
|
||||||
|
"title" => @play_review_activity_title,
|
||||||
|
"description" =>
|
||||||
|
"Synthetic Google Play review meetup in a public place. No real attendance is expected.",
|
||||||
|
"structured_data" => %{"film_or_genre" => "Synthetic public screening"},
|
||||||
|
"location_label" => "Kyiv public cinema area — synthetic data",
|
||||||
|
"latitude" => 50.4508,
|
||||||
|
"longitude" => 30.5248,
|
||||||
|
"location_visibility" => "approximate_public",
|
||||||
|
"starts_at" => DateTime.add(now, 120, :day),
|
||||||
|
"join_deadline" => DateTime.add(now, 110, :day),
|
||||||
|
"capacity" => 4,
|
||||||
|
"category_id" => category.id,
|
||||||
|
"safety_confirmed" => true
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp reusable_or_create_activity(_finished_activity, _reviewer, _category),
|
||||||
|
do: {:error, :review_activity_not_reusable}
|
||||||
|
|
||||||
|
defp ensure_play_review_participant(activity, counterpart) do
|
||||||
|
case WhoNeedHelp.Repo.get_by(WhoNeedHelp.Activities.Participant,
|
||||||
|
activity_id: activity.id,
|
||||||
|
user_id: counterpart.id
|
||||||
|
) do
|
||||||
|
nil ->
|
||||||
|
WhoNeedHelp.Activities.request_to_join(
|
||||||
|
WhoNeedHelp.Accounts.Scope.for_user(counterpart),
|
||||||
|
activity.id
|
||||||
|
)
|
||||||
|
|
||||||
|
%{status: status} = participant when status in [:requested, :approved] ->
|
||||||
|
{:ok, participant}
|
||||||
|
|
||||||
|
_participant ->
|
||||||
|
{:error, :review_participant_not_reusable}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp approve_play_review_participant(_activity, %{status: :approved} = participant, _reviewer),
|
||||||
|
do: {:ok, participant}
|
||||||
|
|
||||||
|
defp approve_play_review_participant(activity, %{status: :requested} = participant, reviewer) do
|
||||||
|
case WhoNeedHelp.Activities.approve_participant(
|
||||||
|
WhoNeedHelp.Accounts.Scope.for_user(reviewer),
|
||||||
|
participant.id
|
||||||
|
) do
|
||||||
|
{:ok, _activity} ->
|
||||||
|
{:ok,
|
||||||
|
WhoNeedHelp.Repo.get_by!(WhoNeedHelp.Activities.Participant,
|
||||||
|
activity_id: activity.id,
|
||||||
|
user_id: participant.user_id
|
||||||
|
)}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def await_migrations do
|
def await_migrations do
|
||||||
load_app()
|
load_app()
|
||||||
Enum.each(repos(), &await_repo/1)
|
Enum.each(repos(), &await_repo/1)
|
||||||
|
|
|
||||||
100
scripts/prepare-play-review.sh
Executable file
100
scripts/prepare-play-review.sh
Executable file
|
|
@ -0,0 +1,100 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
|
EXPECTED_ROOT=/srv/who_need_help-production
|
||||||
|
EXPECTED_PROJECT=who_need_help_production
|
||||||
|
EXPECTED_ORIGIN=https://whoneedhelp.com
|
||||||
|
|
||||||
|
if [ "$#" -ne 5 ] || [ "$3" != "--confirm" ] || [ "$4" != "whoneedhelp.com" ]; then
|
||||||
|
echo "Usage: $0 REVIEWER_EMAIL COUNTERPART_EMAIL --confirm whoneedhelp.com ENV_FILE" >&2
|
||||||
|
echo "Both existing accounts must be active, confirmed, password-enabled, and non-staff." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
REVIEWER_EMAIL=$1
|
||||||
|
COUNTERPART_EMAIL=$2
|
||||||
|
ENV_FILE=$5
|
||||||
|
|
||||||
|
if [ "$(realpath --canonicalize-existing "$ROOT")" != "$EXPECTED_ROOT" ]; then
|
||||||
|
echo "Play review fixtures must run from $EXPECTED_ROOT." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$ENV_FILE" ]; then
|
||||||
|
echo "Environment file does not exist: $ENV_FILE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
read_env() {
|
||||||
|
key=$1
|
||||||
|
awk -F= -v key="$key" '
|
||||||
|
$1 == key {
|
||||||
|
value = substr($0, index($0, "=") + 1)
|
||||||
|
sub(/\r$/, "", value)
|
||||||
|
|
||||||
|
if ((value ~ /^".*"$/) || (value ~ /^\047.*\047$/)) {
|
||||||
|
value = substr(value, 2, length(value) - 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
END {
|
||||||
|
if (count == 1) print value
|
||||||
|
else exit 1
|
||||||
|
}
|
||||||
|
' "$ENV_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$(read_env DEPLOYMENT_ENV)" != production ]; then
|
||||||
|
echo "Play review fixtures may only target DEPLOYMENT_ENV=production." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
project=$(read_env COMPOSE_PROJECT_NAME)
|
||||||
|
if [ "$project" != "$EXPECTED_PROJECT" ]; then
|
||||||
|
echo "Unexpected production Compose project: $project" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$(read_env WNH_BASE_URL)" != "$EXPECTED_ORIGIN" ]; then
|
||||||
|
echo "Production origin must be $EXPECTED_ORIGIN." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
container=$("$ROOT/scripts/compose.sh" "$ENV_FILE" ps -q app | head -n 1)
|
||||||
|
|
||||||
|
if [ -z "$container" ]; then
|
||||||
|
container=$("$ROOT/scripts/compose.sh" "$ENV_FILE" ps -q web | head -n 1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$container" ]; then
|
||||||
|
echo "No running production app or web container was found for project $project." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
expected_image=$(read_env APP_IMAGE)
|
||||||
|
observed_image=$(docker inspect --format '{{.Config.Image}}' "$container")
|
||||||
|
container_state=$(docker inspect --format '{{.State.Status}}' "$container")
|
||||||
|
container_health=$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container")
|
||||||
|
|
||||||
|
if [ "$observed_image" != "$expected_image" ]; then
|
||||||
|
echo "Running container image does not match APP_IMAGE." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$container_state" != running ] || [ "$container_health" != healthy ]; then
|
||||||
|
echo "Production application container is not running and healthy." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
encode() {
|
||||||
|
printf %s "$1" | base64 | tr -d '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
reviewer=$(encode "$REVIEWER_EMAIL")
|
||||||
|
counterpart=$(encode "$COUNTERPART_EMAIL")
|
||||||
|
expression="case WhoNeedHelp.Release.prepare_play_review(Base.decode64!(\"$reviewer\"), Base.decode64!(\"$counterpart\")) do {:ok, result} -> IO.inspect(result); {:error, reason} -> raise \"Play review preparation failed: #{inspect(reason)}\" end"
|
||||||
|
|
||||||
|
docker exec "$container" /app/bin/who_need_help rpc "$expression"
|
||||||
93
test/who_need_help/release_test.exs
Normal file
93
test/who_need_help/release_test.exs
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
defmodule WhoNeedHelp.ReleaseTest do
|
||||||
|
use WhoNeedHelp.DataCase, async: false
|
||||||
|
|
||||||
|
import Ecto.Query
|
||||||
|
import WhoNeedHelp.AccountsFixtures
|
||||||
|
|
||||||
|
alias WhoNeedHelp.Accounts
|
||||||
|
alias WhoNeedHelp.Activities.{Activity, Participant}
|
||||||
|
alias WhoNeedHelp.Catalog
|
||||||
|
alias WhoNeedHelp.Help.{Assignment, HelpRequest}
|
||||||
|
alias WhoNeedHelp.Release
|
||||||
|
alias WhoNeedHelp.Repo
|
||||||
|
|
||||||
|
setup do
|
||||||
|
Catalog.seed_defaults()
|
||||||
|
|
||||||
|
reviewer = user_fixture(display_name: "Play reviewer") |> set_password()
|
||||||
|
counterpart = user_fixture(display_name: "Play counterpart") |> set_password()
|
||||||
|
|
||||||
|
%{reviewer: reviewer, counterpart: counterpart}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "prepares stable non-staff reviewer records idempotently", %{
|
||||||
|
reviewer: reviewer,
|
||||||
|
counterpart: counterpart
|
||||||
|
} do
|
||||||
|
assert {:ok, first} = Release.prepare_play_review(reviewer.email, counterpart.email)
|
||||||
|
assert {:ok, second} = Release.prepare_play_review(reviewer.email, counterpart.email)
|
||||||
|
assert first == second
|
||||||
|
|
||||||
|
assert first.request_path == "/requests/#{first.request_id}"
|
||||||
|
assert first.activity_path == "/activities/#{first.activity_id}"
|
||||||
|
assert Accounts.staff_roles(reviewer) == []
|
||||||
|
assert Accounts.staff_roles(counterpart) == []
|
||||||
|
|
||||||
|
assert %HelpRequest{status: :matched, requester_id: reviewer_id} =
|
||||||
|
Repo.get!(HelpRequest, first.request_id)
|
||||||
|
|
||||||
|
assert reviewer_id == reviewer.id
|
||||||
|
|
||||||
|
assert %Assignment{helper_id: counterpart_id, active: true} =
|
||||||
|
Repo.get_by!(Assignment, request_id: first.request_id)
|
||||||
|
|
||||||
|
assert counterpart_id == counterpart.id
|
||||||
|
|
||||||
|
assert %Activity{status: :open, creator_id: ^reviewer_id} =
|
||||||
|
Repo.get!(Activity, first.activity_id)
|
||||||
|
|
||||||
|
assert %Participant{status: :approved, user_id: ^counterpart_id} =
|
||||||
|
Repo.get_by!(Participant,
|
||||||
|
activity_id: first.activity_id,
|
||||||
|
user_id: counterpart.id
|
||||||
|
)
|
||||||
|
|
||||||
|
assert Repo.aggregate(
|
||||||
|
from(request in HelpRequest,
|
||||||
|
where:
|
||||||
|
request.requester_id == ^reviewer.id and
|
||||||
|
request.title == "Play review — medicine pickup"
|
||||||
|
),
|
||||||
|
:count
|
||||||
|
) == 1
|
||||||
|
|
||||||
|
assert Repo.aggregate(
|
||||||
|
from(activity in Activity,
|
||||||
|
where:
|
||||||
|
activity.creator_id == ^reviewer.id and
|
||||||
|
activity.title == "Play review — public cinema meetup"
|
||||||
|
),
|
||||||
|
:count
|
||||||
|
) == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "rejects an account without a reusable password", %{
|
||||||
|
reviewer: reviewer,
|
||||||
|
counterpart: counterpart
|
||||||
|
} do
|
||||||
|
passwordless = user_fixture(display_name: "Passwordless reviewer")
|
||||||
|
|
||||||
|
assert {:error, {:reviewer, :password_not_set}} =
|
||||||
|
Release.prepare_play_review(passwordless.email, counterpart.email)
|
||||||
|
|
||||||
|
assert {:error, :accounts_must_be_distinct} =
|
||||||
|
Release.prepare_play_review(reviewer.email, reviewer.email)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "rejects staff accounts", %{counterpart: counterpart} do
|
||||||
|
staff = staff_user_fixture([:support], display_name: "Staff reviewer") |> set_password()
|
||||||
|
|
||||||
|
assert {:error, {:reviewer, :staff_account_forbidden}} =
|
||||||
|
Release.prepare_play_review(staff.email, counterpart.email)
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in New Issue
Block a user