182 lines
6.9 KiB
Python
182 lines
6.9 KiB
Python
from __future__ import annotations
|
|
|
|
import unittest
|
|
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import Session, sessionmaker
|
|
|
|
from govoplan_core.db.base import Base
|
|
from govoplan_poll.backend.db.models import Poll, PollOption, PollResponse
|
|
from govoplan_poll.backend.schemas import PollAnswerInput, PollCreateRequest, PollOptionInput, PollSubmitResponseRequest
|
|
from govoplan_poll.backend.service import PollError, create_poll, open_poll, poll_result_summary_by_id, submit_poll_response
|
|
|
|
|
|
class PollServiceTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.engine = create_engine("sqlite:///:memory:")
|
|
Base.metadata.create_all(self.engine, tables=[Poll.__table__, PollOption.__table__, PollResponse.__table__])
|
|
self.Session = sessionmaker(bind=self.engine)
|
|
self.session: Session = self.Session()
|
|
|
|
def tearDown(self) -> None:
|
|
self.session.close()
|
|
Base.metadata.drop_all(self.engine, tables=[PollResponse.__table__, PollOption.__table__, Poll.__table__])
|
|
self.engine.dispose()
|
|
|
|
def test_single_choice_response_can_update_existing_respondent(self) -> None:
|
|
poll = create_poll(
|
|
self.session,
|
|
tenant_id="tenant-1",
|
|
user_id="user-1",
|
|
payload=PollCreateRequest(
|
|
title="Lunch",
|
|
kind="single_choice",
|
|
options=[PollOptionInput(key="a", label="A"), PollOptionInput(key="b", label="B")],
|
|
),
|
|
)
|
|
open_poll(self.session, tenant_id="tenant-1", poll_id=poll.id)
|
|
|
|
first = submit_poll_response(
|
|
self.session,
|
|
tenant_id="tenant-1",
|
|
poll_id=poll.id,
|
|
payload=PollSubmitResponseRequest(respondent_id="person-1", answers=[PollAnswerInput(option_key="a")]),
|
|
)
|
|
second = submit_poll_response(
|
|
self.session,
|
|
tenant_id="tenant-1",
|
|
poll_id=poll.id,
|
|
payload=PollSubmitResponseRequest(respondent_id="person-1", answers=[PollAnswerInput(option_key="b")]),
|
|
)
|
|
summary = poll_result_summary_by_id(self.session, tenant_id="tenant-1", poll_id=poll.id)
|
|
|
|
self.assertEqual(first.id, second.id)
|
|
self.assertEqual(summary["response_count"], 1)
|
|
self.assertEqual(summary["leading_option_ids"], [second.answers[0]["option_id"]])
|
|
|
|
def test_anonymous_response_requires_poll_policy(self) -> None:
|
|
poll = create_poll(
|
|
self.session,
|
|
tenant_id="tenant-1",
|
|
user_id=None,
|
|
payload=PollCreateRequest(
|
|
title="Decision",
|
|
kind="yes_no",
|
|
),
|
|
)
|
|
open_poll(self.session, tenant_id="tenant-1", poll_id=poll.id)
|
|
|
|
with self.assertRaisesRegex(PollError, "Anonymous responses are not allowed"):
|
|
submit_poll_response(
|
|
self.session,
|
|
tenant_id="tenant-1",
|
|
poll_id=poll.id,
|
|
payload=PollSubmitResponseRequest(answers=[PollAnswerInput(option_key="yes")]),
|
|
)
|
|
|
|
def test_ranked_choice_summary_uses_borda_score(self) -> None:
|
|
poll = create_poll(
|
|
self.session,
|
|
tenant_id="tenant-1",
|
|
user_id="user-1",
|
|
payload=PollCreateRequest(
|
|
title="Priority",
|
|
kind="ranked_choice",
|
|
options=[
|
|
PollOptionInput(key="alpha", label="Alpha"),
|
|
PollOptionInput(key="beta", label="Beta"),
|
|
PollOptionInput(key="gamma", label="Gamma"),
|
|
],
|
|
),
|
|
)
|
|
open_poll(self.session, tenant_id="tenant-1", poll_id=poll.id)
|
|
|
|
submit_poll_response(
|
|
self.session,
|
|
tenant_id="tenant-1",
|
|
poll_id=poll.id,
|
|
payload=PollSubmitResponseRequest(
|
|
respondent_id="one",
|
|
answers=[
|
|
PollAnswerInput(option_key="alpha"),
|
|
PollAnswerInput(option_key="beta"),
|
|
PollAnswerInput(option_key="gamma"),
|
|
],
|
|
),
|
|
)
|
|
submit_poll_response(
|
|
self.session,
|
|
tenant_id="tenant-1",
|
|
poll_id=poll.id,
|
|
payload=PollSubmitResponseRequest(
|
|
respondent_id="two",
|
|
answers=[
|
|
PollAnswerInput(option_key="beta"),
|
|
PollAnswerInput(option_key="alpha"),
|
|
PollAnswerInput(option_key="gamma"),
|
|
],
|
|
),
|
|
)
|
|
summary = poll_result_summary_by_id(self.session, tenant_id="tenant-1", poll_id=poll.id)
|
|
scores = {result["option_key"]: result["score"] for result in summary["option_results"]}
|
|
|
|
self.assertEqual(scores["alpha"], 5)
|
|
self.assertEqual(scores["beta"], 5)
|
|
self.assertEqual(scores["gamma"], 2)
|
|
expected_leaders = {
|
|
result["option_id"]
|
|
for result in summary["option_results"]
|
|
if result["option_key"] in {"alpha", "beta"}
|
|
}
|
|
self.assertEqual(set(summary["leading_option_ids"]), expected_leaders)
|
|
|
|
def test_availability_summary_counts_available_and_maybe(self) -> None:
|
|
poll = create_poll(
|
|
self.session,
|
|
tenant_id="tenant-1",
|
|
user_id="user-1",
|
|
payload=PollCreateRequest(
|
|
title="Find a slot",
|
|
kind="availability",
|
|
allow_anonymous=True,
|
|
options=[
|
|
PollOptionInput(key="slot-1", label="Monday", value={"start": "2026-07-13T09:00:00Z"}),
|
|
PollOptionInput(key="slot-2", label="Tuesday", value={"start": "2026-07-14T09:00:00Z"}),
|
|
],
|
|
),
|
|
)
|
|
open_poll(self.session, tenant_id="tenant-1", poll_id=poll.id)
|
|
|
|
submit_poll_response(
|
|
self.session,
|
|
tenant_id="tenant-1",
|
|
poll_id=poll.id,
|
|
payload=PollSubmitResponseRequest(
|
|
answers=[
|
|
PollAnswerInput(option_key="slot-1", value="available"),
|
|
PollAnswerInput(option_key="slot-2", value="maybe"),
|
|
]
|
|
),
|
|
)
|
|
submit_poll_response(
|
|
self.session,
|
|
tenant_id="tenant-1",
|
|
poll_id=poll.id,
|
|
payload=PollSubmitResponseRequest(
|
|
answers=[
|
|
PollAnswerInput(option_key="slot-1", value="unavailable"),
|
|
PollAnswerInput(option_key="slot-2", value="available"),
|
|
]
|
|
),
|
|
)
|
|
summary = poll_result_summary_by_id(self.session, tenant_id="tenant-1", poll_id=poll.id)
|
|
values = {result["option_key"]: result["values"] for result in summary["option_results"]}
|
|
|
|
self.assertEqual(summary["response_count"], 2)
|
|
self.assertEqual(values["slot-1"], {"available": 1, "unavailable": 1})
|
|
self.assertEqual(values["slot-2"], {"maybe": 1, "available": 1})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|