267 lines
10 KiB
Python
267 lines
10 KiB
Python
"""Tests for customer management tools."""
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from shopify import state as shopify_state
|
|
from shopify.models import (
|
|
CreateCustomerArgs,
|
|
GetCustomerArgs,
|
|
ListCustomersArgs,
|
|
SearchCustomersArgs,
|
|
UpdateCustomerArgs,
|
|
)
|
|
from shopify.tools.customers import (
|
|
handle_create_customer,
|
|
handle_get_customer,
|
|
handle_list_customers,
|
|
handle_search_customers,
|
|
handle_update_customer,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def shopify_data(tmp_path):
|
|
"""Seed state with existing customers."""
|
|
data_file = tmp_path / "shopify_data.json"
|
|
data_file.write_text(
|
|
json.dumps(
|
|
{
|
|
"products": {},
|
|
"carts": {},
|
|
"orders": {},
|
|
"customers": {
|
|
"cust-1": {
|
|
"id": "cust-1",
|
|
"firstName": "Alice",
|
|
"lastName": "Smith",
|
|
"email": "alice@example.com",
|
|
"phone": "+1111111111",
|
|
"createdAt": "2024-01-01T00:00:00Z",
|
|
"updatedAt": "2024-01-01T00:00:00Z",
|
|
"defaultAddress": {"address1": "123 Main St", "city": "Springfield"},
|
|
"addresses": [{"address1": "123 Main St", "city": "Springfield"}],
|
|
"ordersCount": 5,
|
|
"totalSpent": {"amount": "250.00", "currencyCode": "USD"},
|
|
"tags": ["vip", "wholesale"],
|
|
"note": None,
|
|
"acceptsMarketing": True,
|
|
"state": "ENABLED",
|
|
},
|
|
"cust-2": {
|
|
"id": "cust-2",
|
|
"firstName": "Bob",
|
|
"lastName": "Jones",
|
|
"email": "bob@example.com",
|
|
"phone": "+2222222222",
|
|
"createdAt": "2024-02-01T00:00:00Z",
|
|
"updatedAt": "2024-02-01T00:00:00Z",
|
|
"defaultAddress": None,
|
|
"addresses": [],
|
|
"ordersCount": 0,
|
|
"totalSpent": {"amount": "0.00", "currencyCode": "USD"},
|
|
"tags": [],
|
|
"note": None,
|
|
"acceptsMarketing": False,
|
|
"state": "ENABLED",
|
|
},
|
|
},
|
|
"policies": [],
|
|
"counters": {
|
|
"cart_id": 1000,
|
|
"line_id": 1000,
|
|
"order_id": 2000,
|
|
"line_item_id": 3000,
|
|
"customer_id": 4000,
|
|
},
|
|
}
|
|
)
|
|
)
|
|
return data_file
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _patch_state(shopify_data, monkeypatch):
|
|
monkeypatch.setattr(shopify_state, "_STATE_FILE", shopify_data)
|
|
shopify_state._current_state = None
|
|
shopify_state._stores.clear()
|
|
shopify_state._active_store_id = "default"
|
|
shopify_state.load_state()
|
|
|
|
|
|
class TestCreateCustomer:
|
|
def test_create_customer(self):
|
|
result = handle_create_customer(
|
|
CreateCustomerArgs(email="carol@example.com", first_name="Carol", last_name="White")
|
|
)
|
|
assert result["userErrors"] == []
|
|
c = result["customer"]
|
|
assert c["id"].startswith("gid://shopify/Customer/")
|
|
assert c["email"] == "carol@example.com"
|
|
assert c["firstName"] == "Carol"
|
|
assert c["state"] == "ENABLED"
|
|
assert c["ordersCount"] == 0
|
|
|
|
def test_create_customer_with_address(self):
|
|
addr = {"firstName": "Carol", "address1": "456 Oak Ave", "city": "Portland"}
|
|
result = handle_create_customer(
|
|
CreateCustomerArgs.model_validate({"email": "carol@example.com", "address": addr})
|
|
)
|
|
c = result["customer"]
|
|
assert c["defaultAddress"]["address1"] == "456 Oak Ave"
|
|
assert len(c["addresses"]) == 1
|
|
|
|
def test_create_customer_with_tags(self):
|
|
result = handle_create_customer(CreateCustomerArgs(email="carol@example.com", tags=["new", "referral"]))
|
|
assert result["customer"]["tags"] == ["new", "referral"]
|
|
|
|
def test_create_customer_duplicate_email(self):
|
|
result = handle_create_customer(CreateCustomerArgs(email="alice@example.com"))
|
|
assert result["customer"] is None
|
|
assert len(result["userErrors"]) == 1
|
|
assert "already exists" in result["userErrors"][0]["message"]
|
|
|
|
|
|
class TestGetCustomer:
|
|
def test_get_existing(self):
|
|
result = handle_get_customer(GetCustomerArgs(customer_id="cust-1"))
|
|
assert result["userErrors"] == []
|
|
assert result["customer"]["email"] == "alice@example.com"
|
|
|
|
def test_get_nonexistent(self):
|
|
result = handle_get_customer(GetCustomerArgs(customer_id="nonexistent"))
|
|
assert result["customer"] is None
|
|
assert len(result["userErrors"]) == 1
|
|
|
|
|
|
class TestListCustomers:
|
|
def test_list_all(self):
|
|
result = handle_list_customers(ListCustomersArgs())
|
|
assert result["totalCount"] == 2
|
|
|
|
def test_list_with_query(self):
|
|
result = handle_list_customers(ListCustomersArgs(query="alice"))
|
|
assert result["totalCount"] == 1
|
|
assert result["customers"][0]["email"] == "alice@example.com"
|
|
|
|
def test_list_with_tag(self):
|
|
result = handle_list_customers(ListCustomersArgs(tag="vip"))
|
|
assert result["totalCount"] == 1
|
|
assert result["customers"][0]["id"] == "cust-1"
|
|
|
|
def test_list_no_match(self):
|
|
result = handle_list_customers(ListCustomersArgs(query="zzz"))
|
|
assert result["totalCount"] == 0
|
|
|
|
def test_list_pagination(self):
|
|
result = handle_list_customers(ListCustomersArgs(limit=1))
|
|
assert len(result["customers"]) == 1
|
|
assert result["totalCount"] == 2
|
|
assert result["pageInfo"]["hasNextPage"] is True
|
|
|
|
|
|
class TestUpdateCustomer:
|
|
def test_update_name(self):
|
|
result = handle_update_customer(UpdateCustomerArgs(customer_id="cust-1", first_name="Alicia"))
|
|
assert result["customer"]["firstName"] == "Alicia"
|
|
assert result["customer"]["lastName"] == "Smith" # unchanged
|
|
|
|
def test_update_tags(self):
|
|
result = handle_update_customer(UpdateCustomerArgs(customer_id="cust-2", tags=["new-tag"]))
|
|
assert result["customer"]["tags"] == ["new-tag"]
|
|
|
|
def test_update_email_rejects_duplicate(self):
|
|
result = handle_update_customer(UpdateCustomerArgs(customer_id="cust-2", email="alice@example.com"))
|
|
assert result["customer"] is None
|
|
assert result["userErrors"][0]["field"] == "email"
|
|
assert "already exists" in result["userErrors"][0]["message"]
|
|
assert shopify_state.get_state().customers["cust-2"].email == "bob@example.com"
|
|
|
|
def test_update_email_rejects_duplicate_before_any_mutation(self):
|
|
result = handle_update_customer(
|
|
UpdateCustomerArgs(
|
|
customer_id="cust-2",
|
|
first_name="Robert",
|
|
last_name="Changed",
|
|
email="alice@example.com",
|
|
tags=["mutated"],
|
|
note="should not stick",
|
|
accepts_marketing=True,
|
|
)
|
|
)
|
|
|
|
customer = shopify_state.get_state().customers["cust-2"]
|
|
assert result["customer"] is None
|
|
assert result["userErrors"][0]["field"] == "email"
|
|
assert customer.firstName == "Bob"
|
|
assert customer.lastName == "Jones"
|
|
assert customer.email == "bob@example.com"
|
|
assert customer.tags == []
|
|
assert customer.note is None
|
|
assert customer.acceptsMarketing is False
|
|
|
|
def test_update_address(self):
|
|
addr = {"address1": "789 Elm St", "city": "Denver"}
|
|
result = handle_update_customer(UpdateCustomerArgs.model_validate({"customer_id": "cust-2", "address": addr}))
|
|
assert result["customer"]["defaultAddress"]["address1"] == "789 Elm St"
|
|
assert len(result["customer"]["addresses"]) == 1
|
|
|
|
def test_update_address_dedupes_existing_default_address(self):
|
|
addr = {"address1": "789 Elm St", "city": "Denver"}
|
|
args = UpdateCustomerArgs.model_validate({"customer_id": "cust-2", "address": addr})
|
|
|
|
handle_update_customer(args)
|
|
result = handle_update_customer(args)
|
|
|
|
assert result["customer"]["defaultAddress"]["address1"] == "789 Elm St"
|
|
assert len(result["customer"]["addresses"]) == 1
|
|
|
|
def test_update_nonexistent(self):
|
|
result = handle_update_customer(UpdateCustomerArgs(customer_id="nonexistent", first_name="X"))
|
|
assert result["customer"] is None
|
|
assert len(result["userErrors"]) == 1
|
|
|
|
|
|
class TestSearchCustomers:
|
|
def test_search_by_name(self):
|
|
result = handle_search_customers(SearchCustomersArgs(query="alice"))
|
|
assert result["totalCount"] == 1
|
|
assert result["customers"][0]["firstName"] == "Alice"
|
|
|
|
def test_search_by_email(self):
|
|
result = handle_search_customers(SearchCustomersArgs(query="bob@example"))
|
|
assert result["totalCount"] == 1
|
|
assert result["customers"][0]["firstName"] == "Bob"
|
|
|
|
def test_search_by_phone(self):
|
|
result = handle_search_customers(SearchCustomersArgs(query="+1111"))
|
|
assert result["totalCount"] == 1
|
|
|
|
def test_search_no_match(self):
|
|
result = handle_search_customers(SearchCustomersArgs(query="zzzzz"))
|
|
assert result["totalCount"] == 0
|
|
|
|
def test_search_case_insensitive(self):
|
|
result = handle_search_customers(SearchCustomersArgs(query="ALICE"))
|
|
assert result["totalCount"] == 1
|
|
|
|
def test_search_word_and_first_and_last(self):
|
|
# Multi-word query — all words must appear; "Alice Smith" matches.
|
|
result = handle_search_customers(SearchCustomersArgs(query="alice smith"))
|
|
assert result["totalCount"] == 1
|
|
assert result["customers"][0]["firstName"] == "Alice"
|
|
|
|
def test_search_misses_when_one_word_absent(self):
|
|
# "alice jones" — neither customer has both words present in their record.
|
|
result = handle_search_customers(SearchCustomersArgs(query="alice jones"))
|
|
assert result["totalCount"] == 0
|
|
|
|
def test_search_quoted_phrase_requires_adjacency(self):
|
|
# Full adjacent phrase hits; reversed phrase misses.
|
|
hit = handle_search_customers(SearchCustomersArgs(query='"alice smith"'))
|
|
assert hit["totalCount"] == 1
|
|
|
|
miss = handle_search_customers(SearchCustomersArgs(query='"smith alice"'))
|
|
assert miss["totalCount"] == 0
|