146 lines
5.7 KiB
Python
146 lines
5.7 KiB
Python
"""
|
|
Copyright 2024-2026 ChatterMate
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
Handoff contact capture: which fields the form asks for, and what happens to an
|
|
address the customer row is not allowed to take.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from app.models.customer import Customer
|
|
from app.repositories.customer import CustomerRepository
|
|
from app.services.contact_capture import (
|
|
CONTACT_EMAIL_META_KEY,
|
|
build_handoff_contact_form,
|
|
retain_unstored_email,
|
|
)
|
|
|
|
|
|
def _anon(db, org_id, email="1786455337798@noemail.com"):
|
|
c = Customer(organization_id=org_id, email=email)
|
|
db.add(c)
|
|
db.commit()
|
|
db.refresh(c)
|
|
return c
|
|
|
|
|
|
# ---------- which fields the form asks for ----------
|
|
|
|
@pytest.mark.parametrize("email,name,expected", [
|
|
(None, None, ["email", "name"]), # nothing known
|
|
("abc@noemail.com", None, ["email", "name"]), # anonymous placeholder
|
|
("123@telegram.channel", None, ["email", "name"]), # synthesized channel key
|
|
("abc@noemail.com", "Arun", ["email"]), # name already known
|
|
("real@acme.com", None, ["name"]), # address already known
|
|
])
|
|
def test_form_asks_only_for_what_is_missing(email, name, expected):
|
|
from types import SimpleNamespace
|
|
form = build_handoff_contact_form(
|
|
SimpleNamespace(email=email, full_name=name), collect_email=True, collect_name=True
|
|
)
|
|
assert [f["name"] for f in form["fields"]] == expected
|
|
|
|
|
|
def test_no_form_when_nothing_is_missing():
|
|
from types import SimpleNamespace
|
|
assert build_handoff_contact_form(
|
|
SimpleNamespace(email="real@acme.com", full_name="Arun"), True, True
|
|
) is None
|
|
|
|
|
|
def test_no_form_when_both_toggles_are_off():
|
|
from types import SimpleNamespace
|
|
assert build_handoff_contact_form(
|
|
SimpleNamespace(email=None, full_name=None), collect_email=False, collect_name=False
|
|
) is None
|
|
|
|
|
|
# ---------- an address the customer row cannot take ----------
|
|
|
|
def test_email_owned_by_another_customer_is_kept_on_meta_data(db, test_organization):
|
|
"""The reported bug. A returning anonymous visitor (or a shared team inbox)
|
|
submits an address another customer in the org already owns; update_contact
|
|
refuses it for the unique constraint, and it used to be dropped on the floor
|
|
— the visitor asked to be contacted and nothing recorded how."""
|
|
_anon(db, test_organization.id, email="taken@acme.com")
|
|
visitor = _anon(db, test_organization.id)
|
|
repo = CustomerRepository(db)
|
|
|
|
result = repo.update_contact(visitor.id, email="taken@acme.com", full_name=None)
|
|
assert result["email_updated"] is False # constraint held, as designed
|
|
|
|
assert retain_unstored_email(repo, visitor.id, "taken@acme.com", result) is True
|
|
|
|
db.refresh(visitor)
|
|
assert visitor.meta_data[CONTACT_EMAIL_META_KEY] == "taken@acme.com"
|
|
|
|
|
|
def test_name_only_submission_still_retains_the_dropped_email(db, test_organization):
|
|
"""Submitting a name too made the panel visibly change, which is what made
|
|
this look name-dependent — the email was being lost either way."""
|
|
_anon(db, test_organization.id, email="taken2@acme.com")
|
|
visitor = _anon(db, test_organization.id)
|
|
repo = CustomerRepository(db)
|
|
|
|
result = repo.update_contact(visitor.id, email="taken2@acme.com", full_name="Runix")
|
|
assert result["email_updated"] is False
|
|
assert result["name_updated"] is True
|
|
|
|
assert retain_unstored_email(repo, visitor.id, "taken2@acme.com", result) is True
|
|
db.refresh(visitor)
|
|
assert visitor.full_name == "Runix"
|
|
assert visitor.meta_data[CONTACT_EMAIL_META_KEY] == "taken2@acme.com"
|
|
|
|
|
|
def test_a_normal_capture_writes_no_metadata(db, test_organization):
|
|
"""The address landed on the customer, so there is nothing to retain and the
|
|
panel shouldn't grow a duplicate line."""
|
|
visitor = _anon(db, test_organization.id)
|
|
repo = CustomerRepository(db)
|
|
|
|
result = repo.update_contact(visitor.id, email="fresh@acme.com", full_name=None)
|
|
assert result["email_updated"] is True
|
|
|
|
assert retain_unstored_email(repo, visitor.id, "fresh@acme.com", result) is False
|
|
db.refresh(visitor)
|
|
assert not (visitor.meta_data or {}).get(CONTACT_EMAIL_META_KEY)
|
|
|
|
|
|
def test_resubmitting_the_address_already_on_file_is_a_no_op(db, test_organization):
|
|
visitor = _anon(db, test_organization.id, email="mine@acme.com")
|
|
repo = CustomerRepository(db)
|
|
|
|
result = repo.update_contact(visitor.id, email="mine@acme.com", full_name=None)
|
|
|
|
assert retain_unstored_email(repo, visitor.id, "mine@acme.com", result) is False
|
|
db.refresh(visitor)
|
|
assert not (visitor.meta_data or {}).get(CONTACT_EMAIL_META_KEY)
|
|
|
|
|
|
def test_retaining_preserves_existing_meta_data(db, test_organization):
|
|
"""Integrator-supplied custom_data must survive — update_meta_data merges."""
|
|
_anon(db, test_organization.id, email="taken3@acme.com")
|
|
visitor = _anon(db, test_organization.id)
|
|
visitor.meta_data = {"student_name": "Priya"}
|
|
db.commit()
|
|
repo = CustomerRepository(db)
|
|
|
|
result = repo.update_contact(visitor.id, email="taken3@acme.com", full_name=None)
|
|
retain_unstored_email(repo, visitor.id, "taken3@acme.com", result)
|
|
|
|
db.refresh(visitor)
|
|
assert visitor.meta_data["student_name"] == "Priya"
|
|
assert visitor.meta_data[CONTACT_EMAIL_META_KEY] == "taken3@acme.com"
|