Commit a17ecc59 authored by Dominique Merle's avatar Dominique Merle 💬
Browse files

A Poll has Candidates, not Options.

Tough refacto ; the test suite passes but there may still be bugs left.
parent 0674459d
......@@ -100,7 +100,7 @@ QUERY;
/**
* This step makes multiple HTTP queries, one per poll option.
* This step makes multiple HTTP queries, one per poll candidate.
* Use:
* Then <actor> prints the last <count> transactions
*
......@@ -115,20 +115,20 @@ QUERY;
$pollId = $poll->getId();
$opinions = [];
foreach ($data as $optionTitle => $localizedMention) {
$pollOption = $this->findOneLimajuPollOptionFromTitleAndPoll($optionTitle, $poll);
foreach ($data as $candidateTitle => $localizedMention) {
$pollCandidate = $this->findOneLimajuPollCandidateFromTitleAndPoll($candidateTitle, $poll);
$mention = $this->unlocalizeLimajuPollMention($localizedMention);
$opinions[(string)$pollOption->getId()] = $mention;
$opinions[(string)$pollCandidate->getId()] = $mention;
$query = <<<'QUERY'
mutation voteOnLimajuPoll($optionIri: String!, $mention: String!) {
createLimajuPollOptionVote(input: { option: $optionIri, mention: $mention }) {
LimajuPollOptionVote { id, author { id } }
mutation voteOnLimajuPoll($candidateIri: String!, $mention: String!) {
createLimajuPollCandidateVote(input: { candidate: $candidateIri, mention: $mention }) {
LimajuPollCandidateVote { id, author { id } }
}
}
QUERY;
$variables = array(
'optionIri' => $this->iri($pollOption),
'candidateIri' => $this->iri($pollCandidate),
'mention' => $mention,
);
$this->actor($actor)->gqlNew($query, $variables, !empty($try));
......@@ -150,13 +150,13 @@ QUERY;
{
$data = $this->yaml($pystring);
$options = [];
if (isset($data[$this->t('keys.poll.options')])) {
foreach ($data[$this->t('keys.poll.options')] as $optionDatum) {
if (is_string($optionDatum)) {
$optionDatum = ['title' => $optionDatum];
$candidates = [];
if (isset($data[$this->t('keys.poll.candidates')])) {
foreach ($data[$this->t('keys.poll.candidates')] as $candidateDatum) {
if (is_string($candidateDatum)) {
$candidateDatum = ['title' => $candidateDatum];
}
$options[] = $optionDatum;
$candidates[] = $candidateDatum;
}
}
......@@ -174,23 +174,23 @@ QUERY;
$variables = [
'input' => [
'title' => $data[$this->t('keys.poll.title')],
// 'options' => $options,
// 'candidates' => $candidates,
],
];
$tx = $this->actor($actor)->gqlNew($query, $variables, !empty($try));
// Since we can't manage to post options as well in one single request,
// Since we can't manage to post candidates as well in one single request,
// even though we configured the graphql denormalization just like REST.
// Maybe we're missing something… Time will tell.
// Anyhow, we're posting the options afterwards, and we use the returned poll's IRI.
// Anyhow, we're posting the candidates afterwards, and we use the returned poll's IRI.
$response = json_decode($tx->getResponse()->getContent());
$pollIri = $response->data->createLimajuPoll->LimajuPoll->id;
foreach ($options as $option) {
foreach ($candidates as $candidate) {
$query = <<<'QUERY'
mutation createLimajuPollOption($input: createLimajuPollOptionInput!) {
createLimajuPollOption(input: $input) {
LimajuPollOption {
mutation createLimajuPollCandidate($input: createLimajuPollCandidateInput!) {
createLimajuPollCandidate(input: $input) {
LimajuPollCandidate {
id
title
}
......@@ -199,7 +199,7 @@ mutation createLimajuPollOption($input: createLimajuPollOptionInput!) {
QUERY;
$variables = [
'input' => [
'title' => $option['title'],
'title' => $candidate['title'],
'poll' => $pollIri,
],
];
......
......@@ -49,15 +49,15 @@ class ApiRestFeatureContext extends BaseFeatureContext
$poll = $this->findOneLimajuPollFromTitle($title);
$data = $this->yaml($pystring);
foreach ($data as $optionTitle => $localizedMention) {
$pollOption = $this->findOneLimajuPollOptionFromTitleAndPoll($optionTitle, $poll);
foreach ($data as $candidateTitle => $localizedMention) {
$pollCandidate = $this->findOneLimajuPollCandidateFromTitleAndPoll($candidateTitle, $poll);
$mention = $this->unlocalizeLimajuPollMention($localizedMention);
$this->actor($actor)->api(
'POST',"/limaju_poll_option_votes",
'POST',"/limaju_poll_candidate_votes",
[
// the author is inferred from auth
// 'author' => $this->iri($this->actor($actor)->getUser()),
'option' => $this->iri($pollOption),
'candidate' => $this->iri($pollCandidate),
'mention' => $mention,
], [], !empty($try)
);
......@@ -73,13 +73,13 @@ class ApiRestFeatureContext extends BaseFeatureContext
{
$data = $this->yaml($pystring);
$options = [];
if (isset($data[$this->t('keys.poll.options')])) {
foreach ($data[$this->t('keys.poll.options')] as $optionDatum) {
if (is_string($optionDatum)) {
$optionDatum = ['title' => $optionDatum];
$candidates = [];
if (isset($data[$this->t('keys.poll.candidates')])) {
foreach ($data[$this->t('keys.poll.candidates')] as $candidateDatum) {
if (is_string($candidateDatum)) {
$candidateDatum = ['title' => $candidateDatum];
}
$options[] = $optionDatum;
$candidates[] = $candidateDatum;
}
}
......@@ -87,7 +87,7 @@ class ApiRestFeatureContext extends BaseFeatureContext
'POST',"/limaju_polls",
[
'title' => $data[$this->t('keys.poll.title')],
'options' => $options,
'candidates' => $candidates,
], [], !empty($try)
);
}
......
......@@ -2,31 +2,26 @@
use App\Application;
use App\Entity\User;
use App\Entity\LimajuPoll;
use App\Entity\LimajuPollOption;
use App\Entity\LimajuPollCandidate;
use App\Features\Actor;
use App\Features\Actors;
use App\Features\CliPrinter;
use App\Features\Transaction;
use App\Repository\LimajuPollOptionRepository;
use App\Repository\LimajuPollOptionVoteRepository;
use App\Repository\LimajuPollCandidateRepository;
use App\Repository\LimajuPollCandidateVoteRepository;
use App\Repository\LimajuPollRepository;
use App\Tally\Bot\TallyBotInterface;
use Behat\Behat\Context\Context;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\ORM\EntityManager;
//use FOS\UserBundle\Model\UserManager;
use Doctrine\ORM\EntityRepository;
use Liip\FunctionalTestBundle\Test\WebTestCase;
use MsgPhp\User\Command\CreateUser;
use MsgPhp\User\Infrastructure\Doctrine\Repository\UserRepository;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Yaml\Yaml;
use MsgPhp\User\Command\CreateUser;
use Symfony\Component\Messenger\MessageBusInterface;
//use FOS\UserBundle\Model\UserManager;
/**
......@@ -188,19 +183,19 @@ class BaseFeatureContext extends WebTestCase implements Context
}
/**
* @return LimajuPollOptionRepository
* @return LimajuPollCandidateRepository
*/
protected function getLimajuPollOptionRepository()
protected function getLimajuPollCandidateRepository()
{
return $this->get(LimajuPollOptionRepository::class);
return $this->get(LimajuPollCandidateRepository::class);
}
/**
* @return LimajuPollOptionVoteRepository
* @return LimajuPollCandidateVoteRepository
*/
protected function getLimajuPollOptionVoteRepository()
protected function getLimajuPollCandidateVoteRepository()
{
return $this->get(LimajuPollOptionVoteRepository::class);
return $this->get(LimajuPollCandidateVoteRepository::class);
}
......@@ -391,29 +386,29 @@ class BaseFeatureContext extends WebTestCase implements Context
}
protected function findOneLimajuPollOptionFromId($id, $lenient = false) : ?LimajuPollOption
protected function findOneLimajuPollCandidateFromId($id, $lenient = false) : ?LimajuPollCandidate
{
$pollOption = $this->getRepository(LimajuPollOption::class)->findOneById($id);
if (( ! $lenient) && (null == $pollOption)) {
$this->fail("No LimajuPollOption with Id `$id' could be found.");
$pollCandidate = $this->getRepository(LimajuPollCandidate::class)->findOneById($id);
if (( ! $lenient) && (null == $pollCandidate)) {
$this->fail("No LimajuPollCandidate with Id `$id' could be found.");
}
return $pollOption;
return $pollCandidate;
}
protected function findOneLimajuPollOptionFromTitleAndPoll($title, $poll, $lenient = false) : ?LimajuPollOption
protected function findOneLimajuPollCandidateFromTitleAndPoll($title, $poll, $lenient = false) : ?LimajuPollCandidate
{
/** @var LimajuPollOption $pollOption */
$pollOption = $this->getRepository(LimajuPollOption::class)->findOneBy([
/** @var LimajuPollCandidate $pollCandidate */
$pollCandidate = $this->getRepository(LimajuPollCandidate::class)->findOneBy([
'title' => $title,
'poll' => $poll,
]);
if (( ! $lenient) && (null == $pollOption)) {
$this->failTrans("no_majority_judgment_poll_option_found_for_title", ['title' => $title]);
if (( ! $lenient) && (null == $pollCandidate)) {
$this->failTrans("no_majority_judgment_poll_candidate_found_for_title", ['title' => $title]);
}
return $pollOption;
return $pollCandidate;
}
......
......@@ -7,7 +7,7 @@
use App\Entity\LimajuPoll;
use App\Entity\LimajuPollOption;
use App\Entity\LimajuPollCandidate;
use App\Entity\User;
use MsgPhp\User\Command\AddUserRole;
......@@ -87,7 +87,7 @@ class MainFeatureContext extends BaseFeatureContext
public function givenLimajuPoll($pystring)
{
$titleKey = $this->t('keys.poll.title');
$optionsKey = $this->t('keys.poll.options');
$candidatesKey = $this->t('keys.poll.candidates');
$data = $this->yaml($pystring);
$poll = new LimajuPoll();
......@@ -99,15 +99,15 @@ class MainFeatureContext extends BaseFeatureContext
$poll->setTitle($data[$titleKey]);
$this->persist($poll);
if ( ! isset($data[$optionsKey])) {
$this->fail("At least one option is required in '${optionsKey}:'.");
if ( ! isset($data[$candidatesKey])) {
$this->fail("At least one candidate is required in '${candidatesKey}:'.");
}
foreach ($data[$optionsKey] as $optionTitle) {
$option = new LimajuPollOption();
$option->setTitle($optionTitle);
$poll->addOption($option);
$this->persist($option);
foreach ($data[$candidatesKey] as $candidateTitle) {
$candidate = new LimajuPollCandidate();
$candidate->setTitle($candidateTitle);
$poll->addCandidate($candidate);
$this->persist($candidate);
}
$this->flush();
......@@ -145,15 +145,15 @@ class MainFeatureContext extends BaseFeatureContext
* Then /^there should(?: now)?(?: still)?(?: only)? be (?P<thatMuch>.+) majority judgment polls? in the database$/ui
* @Then /^(?:que?' ?)?(?P<actor>.+?)(?: ne)? d(?:oi|evrai)t(?: maintenant)?(?: encore)? avoir (?P<thatMuch>.+) votes? sur le scrutin(?: au jugement majoritaire)? titré "(?P<title>.+?)"$/ui
*/
public function actorShouldHaveSomeLimajuPollOptionVotesForPoll($actor, $thatMuch, $title)
public function actorShouldHaveSomeLimajuPollCandidateVotesForPoll($actor, $thatMuch, $title)
{
$actor = $this->actor($actor);
$thatMuch = $this->number($thatMuch);
$poll = $this->findOneLimajuPollFromTitle($title);
// fixme: for poll
$votes = $this->getLimajuPollOptionVoteRepository()->findBy([
'author' => $actor->getUser()->getId(),
$votes = $this->getLimajuPollCandidateVoteRepository()->findBy([
'elector' => $actor->getUser()->getId(),
// 'poll' => $poll,
]);
$actual = count($votes);
......@@ -183,11 +183,11 @@ class MainFeatureContext extends BaseFeatureContext
$positionAtom = 'position';
$expected = [];
$pollOptions = [];
foreach ($expectedRaw as $optionTitle => $localizedMentionOrData) {
$pollOption = $this->findOneLimajuPollOptionFromTitleAndPoll($optionTitle, $poll);
$pollOptionId = $pollOption->getId()->toString();
$pollOptions[$pollOptionId] = $pollOption;
$pollcandidates = [];
foreach ($expectedRaw as $candidateTitle => $localizedMentionOrData) {
$pollCandidate = $this->findOneLimajuPollCandidateFromTitleAndPoll($candidateTitle, $poll);
$pollCandidateId = $pollCandidate->getId()->toString();
$pollcandidates[$pollCandidateId] = $pollCandidate;
if ( ! is_array($localizedMentionOrData)) {
$localizedMentionOrData = [
......@@ -196,7 +196,7 @@ class MainFeatureContext extends BaseFeatureContext
}
$localizedMentionOrData[$mentionAtom] = $this->unlocalizeLimajuPollMention($localizedMentionOrData[$mentionAtom]);
$expected[$pollOptionId] = $localizedMentionOrData;
$expected[$pollCandidateId] = $localizedMentionOrData;
}
$tallyBot = $this->getTallyBot($tally);
......@@ -205,31 +205,31 @@ class MainFeatureContext extends BaseFeatureContext
$assertedSomething = false;
$expectationsLeftToProcess = array_keys($expected);
foreach ($actual->options as $optionTally) {
foreach ($actual->candidates as $candidateTally) {
$optionId = $optionTally->poll_option_id->toString();
$candidateId = $candidateTally->poll_candidate_id->toString();
if (isset($expected[$optionId])) {
if (isset($expected[$candidateId])) {
$assertedSomething = true;
$expectationsLeftToProcess = array_diff($expectationsLeftToProcess, [$optionId]);
$pollOption = $this->findOneLimajuPollOptionFromId($optionId);
if ($expected[$optionId][$mentionAtom] !== $optionTally->mention) {
dump("Actual option tally", $optionTally);
$this->failTrans("option_tallies_dont_match", [
'expected_mention' => $this->t('majority_judgment_poll.mention.'.$expected[$optionId][$mentionAtom]),
'actual_mention' => $this->t('majority_judgment_poll.mention.'.$optionTally->mention),
'option' => $pollOption,
$expectationsLeftToProcess = array_diff($expectationsLeftToProcess, [$candidateId]);
$pollCandidate = $this->findOneLimajuPollCandidateFromId($candidateId);
if ($expected[$candidateId][$mentionAtom] !== $candidateTally->mention) {
dump("Actual candidate tally", $candidateTally);
$this->failTrans("candidate_tallies_dont_match", [
'expected_mention' => $this->t('majority_judgment_poll.mention.'.$expected[$candidateId][$mentionAtom]),
'actual_mention' => $this->t('majority_judgment_poll.mention.'.$candidateTally->mention),
'candidate' => $pollCandidate,
]);
}
if (isset($expected[$optionId][$positionAtom])) {
if ($expected[$optionId][$positionAtom] !== $optionTally->position) {
if (isset($expected[$candidateId][$positionAtom])) {
if ($expected[$candidateId][$positionAtom] !== $candidateTally->position) {
dump("Actual poll tally", $actual);
$this->failTrans('option_position_mismatch', [
'expected_position' => $expected[$optionId][$positionAtom],
'actual_position' => $optionTally->position,
'option' => $pollOption,
$this->failTrans('candidate_position_mismatch', [
'expected_position' => $expected[$candidateId][$positionAtom],
'actual_position' => $candidateTally->position,
'candidate' => $pollCandidate,
]);
}
}
......@@ -238,12 +238,12 @@ class MainFeatureContext extends BaseFeatureContext
}
if (0 < count($expectationsLeftToProcess)) {
$optionsLeft = array_map(function($e) use ($pollOptions) {
return $pollOptions[$e];
$candidatesLeft = array_map(function($e) use ($pollcandidates) {
return $pollcandidates[$e];
}, $expectationsLeftToProcess);
$this->failTrans("options_left_unprocessed", [
$this->failTrans("candidates_left_unprocessed", [
'expected' => $expected,
'options' => $optionsLeft,
'candidates' => $candidatesLeft,
]);
}
......
Subproject commit 01cdd212c26e98165cb2bfd73eab909f477fbfdb
Subproject commit 103edf7cacac595aaeff5be74d74133938a87d7d
......@@ -71,13 +71,13 @@ class LimajuPoll
* @Groups({ "create", "read", "update" })
* @ApiSubresource()
* @ORM\OneToMany(
* targetEntity="App\Entity\LimajuPollOption",
* targetEntity="LimajuPollCandidate",
* mappedBy="poll",
* cascade={"persist"},
* orphanRemoval=true,
* )
*/
private $options;
private $candidates;
/**
* @Groups({ "none" })
......@@ -90,7 +90,7 @@ class LimajuPoll
public function __construct()
{
$this->options = new ArrayCollection();
$this->candidates = new ArrayCollection();
}
public function getId(): ?UuidInterface
......@@ -113,30 +113,30 @@ class LimajuPoll
/**
* @return Collection|LimajuPollOption[]
* @return Collection|LimajuPollCandidate[]
*/
public function getOptions(): Collection
public function getCandidates(): Collection
{
return $this->options;
return $this->candidates;
}
public function addOption(LimajuPollOption $option): self
public function addCandidate(LimajuPollCandidate $candidate): self
{
if (!$this->options->contains($option)) {
$this->options[] = $option;
$option->setPoll($this);
if (!$this->candidates->contains($candidate)) {
$this->candidates[] = $candidate;
$candidate->setPoll($this);
}
return $this;
}
public function removeOption(LimajuPollOption $option): self
public function removeCandidate(LimajuPollCandidate $candidate): self
{
if ($this->options->contains($option)) {
$this->options->removeElement($option);
if ($this->candidates->contains($candidate)) {
$this->candidates->removeElement($candidate);
// set the owning side to null (unless already changed)
if ($option->getPoll() === $this) {
$option->setPoll(null);
if ($candidate->getPoll() === $this) {
$candidate->setPoll(null);
}
}
......
......@@ -8,17 +8,34 @@ use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\UuidInterface;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* @ApiResource()
* @ORM\Entity(repositoryClass="App\Repository\LimajuPollOptionRepository")
* A Candidate of a Liquid Majority Judgment Poll whom any Elector can give a Mention to.
*
* @ApiResource(
* normalizationContext={"groups"={"read"}},
* itemOperations={
* "get"={
* "normalization_context"={"groups"={"read"}},
* },
* },
* collectionOperations={
* "post"={
* "denormalization_context"={"groups"={"create"}},
* },
* }
* )
* @ORM\Entity(
* repositoryClass="App\Repository\LimajuPollCandidateRepository",
* )
*/
class LimajuPollOption
class LimajuPollCandidate
{
/**
* @var \Ramsey\Uuid\UuidInterface
* @var UuidInterface
*
* @Groups({ "read" })
* @ORM\Id
......@@ -29,22 +46,22 @@ class LimajuPollOption
private $id;
/**
* @Groups({ "create", "read", "update" })
* @Groups({ "create", "read" })
* @ORM\Column(type="string", length=142)
*/
private $title;
/**
* The poll this option is attached to.
* The poll this candidate is attached to.
*
* @Groups({ "create" })
* @ORM\ManyToOne(targetEntity="App\Entity\LimajuPoll", inversedBy="options")
* @ORM\ManyToOne(targetEntity="App\Entity\LimajuPoll", inversedBy="candidates")
* @ORM\JoinColumn(nullable=false)
*/
private $poll;
// /**
// * @ORM\OneToMany(targetEntity="App\Entity\LimajuOptionVote", mappedBy="option", orphanRemoval=true)
// * @ORM\OneToMany(targetEntity="App\Entity\LimajuCandidateVote", mappedBy="candidate", orphanRemoval=true)
// */
// private $votes;
......@@ -83,30 +100,30 @@ class LimajuPollOption
}
/**
* @return Collection|LimajuPollOptionVote[]
* @return Collection|LimajuPollCandidateVote[]
*/
public function getVotes(): Collection
{
return $this->votes;
}
public function addVote(LimajuPollOptionVote $vote): self
public function addVote(LimajuPollCandidateVote $vote): self
{
if (!$this->votes->contains($vote)) {
$this->votes[] = $vote;
$vote->setOption($this);
$vote->setCandidate($this);
}
return $this;
}
public function removeVote(LimajuPollOptionVote $vote): self
public function removeVote(LimajuPollCandidateVote $vote): self
{
if ($this->votes->contains($vote)) {
$this->votes->removeElement($vote);
// set the owning side to null (unless already changed)
if ($vote->getOption() === $this) {
$vote->setOption(null);
if ($vote->getCandidate() === $this) {
$vote->setCandidate(null);
}
}
......
......@@ -31,9 +31,9 @@ use Symfony\Component\Serializer\Annotation\Groups;
* },
* }
* )
* @ORM\Entity(repositoryClass="App\Repository\LimajuPollOptionVoteRepository")
* @ORM\Entity(repositoryClass="App\Repository\LimajuPollCandidateVoteRepository")
*/
class LimajuPollOptionVote
class LimajuPollCandidateVote
{
/**
* @var UuidInterface
......@@ -48,26 +48,26 @@ class LimajuPollOptionVote
/**
* The Majority Judgment Poll Option the author is giving a mention to.
* The Majority Judgment Poll Candidate the author is giving a mention to.
*
* @Groups({ "vote:create", "vote:read" })
* @ORM\ManyToOne(targetEntity="App\Entity\LimajuPollOption", inversedBy="votes")
* @ORM\ManyToOne(targetEntity="LimajuPollCandidate", inversedBy="votes")
* @ORM\JoinColumn(nullable=false)
*/
private $option;
private $candidate;