diff --git a/memberPlumbing/tests/__init__.py b/memberPlumbing/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/memberPlumbing/tests/json-schemas/usr.schema b/memberPlumbing/tests/json-schemas/usr.schema new file mode 100644 index 0000000..13a8c74 --- /dev/null +++ b/memberPlumbing/tests/json-schemas/usr.schema @@ -0,0 +1,688 @@ +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "properties": { + "nam": { + "type": "string" + }, + "adr": { + "type": "object", + "properties": { + "zip": { + "type": "string" + }, + "cit": { + "type": "string" + }, + "sta": { + "type": "string" + }, + "con": { + "type": "string" + }, + "ad1": { + "type": "string" + }, + "loc": { + "type": "array", + "items": { + "type": "number" + } + } + }, + "required": [ + "ad1", + "cit", + "con", + "loc", + "sta", + "zip" + ] + }, + "org": { + "type": "integer" + }, + "cur": { + "type": "string" + }, + "ctc": { + "type": "string" + }, + "atg": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "typ": { + "type": "string" + }, + "eml": { + "type": "string" + }, + "phn": { + "type": "string" + }, + "end": { + "type": "integer" + }, + "tpl": { + "type": "object", + "properties": { + "anm": { + "type": "array", + "items": { + "type": "object", + "properties": { + "lbl": { + "type": "string" + }, + "box": { + "type": "array", + "items": { + "type": "object", + "properties": { + "dat": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "_id": { + "type": "string" + }, + "typ": { + "type": "integer" + }, + "plh": { + "type": "string" + }, + "req": { + "type": "integer" + }, + "rxp": { + "type": "string" + }, + "err": { + "type": "string" + }, + "lbl": { + "type": "string" + }, + "dtl": { + "type": "string" + } + }, + "required": [ + "typ" + ] + } + } + ] + }, + "ttl": { + "type": "string" + }, + "dtl": { + "type": "string" + } + }, + "required": [ + "dat" + ] + } + }, + "dir": { + "type": "integer" + } + }, + "required": [ + "box", + "lbl" + ] + } + }, + "acc": { + "type": "array", + "items": { + "type": "object", + "properties": { + "lbl": { + "type": "string" + }, + "box": { + "type": "array", + "items": { + "type": "object", + "properties": { + "dat": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "_id": { + "type": "string" + }, + "typ": { + "type": "integer" + }, + "plh": { + "type": "string" + }, + "req": { + "type": "integer" + }, + "rxp": { + "type": "string" + }, + "err": { + "type": "string" + }, + "lbl": { + "type": "string" + } + }, + "required": [ + "_id", + "lbl", + "typ" + ] + } + } + ] + }, + "ttl": { + "type": "string" + } + }, + "required": [ + "dat" + ] + } + } + }, + "required": [ + "box", + "lbl" + ] + } + }, + "adm": { + "type": "array", + "items": { + "type": "object", + "properties": { + "lbl": { + "type": "string" + }, + "box": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ttl": { + "type": "string" + }, + "dat": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "_id": { + "type": "string" + }, + "typ": { + "type": "integer" + }, + "plh": { + "type": "string" + }, + "req": { + "type": "integer" + }, + "rxp": { + "type": "string" + }, + "err": { + "type": "string" + }, + "lbl": { + "type": "string" + }, + "def": { + "type": "string" + } + }, + "required": [ + "_id", + "typ" + ] + } + } + ] + }, + "dtl": { + "type": "string" + } + }, + "required": [ + "dat" + ] + } + } + }, + "required": [ + "box", + "lbl" + ] + } + }, + "dir": { + "type": "array", + "items": { + "type": "object", + "properties": { + "lbl": { + "type": "string" + }, + "box": { + "type": "array", + "items": { + "type": "object", + "properties": { + "htm": { + "type": "string" + }, + "dat": { + "type": "string" + } + } + } + } + }, + "required": [ + "box", + "lbl" + ] + } + }, + "crd": { + "type": "object", + "properties": { + "typ": { + "type": "integer" + }, + "dtl": { + "type": "string" + } + }, + "required": [ + "dtl", + "typ" + ] + }, + "ylp": { + "type": "array", + "items": { + "type": "object", + "properties": { + "dat": { + "type": "string" + }, + "ttl": { + "type": "string" + } + }, + "required": [ + "dat" + ] + } + }, + "evt": { + "type": "array", + "items": { + "type": "object", + "properties": { + "_id": { + "type": "string" + }, + "typ": { + "type": "integer" + }, + "req": { + "type": "integer" + }, + "lbl": { + "type": "string" + }, + "rxp": { + "type": "string" + }, + "err": { + "type": "string" + } + }, + "required": [ + "_id", + "lbl", + "req", + "typ" + ] + } + } + }, + "required": [ + "acc", + "adm", + "anm", + "crd", + "dir", + "evt", + "ylp" + ] + }, + "evg": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ttl": { + "type": "string" + }, + "cal": { + "type": "integer" + }, + "opt": { + "type": "integer" + } + }, + "required": [ + "cal", + "opt", + "ttl" + ] + } + }, + "dcc": { + "type": "array", + "items": { + "type": "object", + "properties": { + "lbl": { + "type": "string" + }, + "amt": { + "type": "integer" + }, + "typ": { + "type": "integer" + }, + "_id": { + "type": "string" + }, + "cnt": { + "type": "integer" + }, + "cap": { + "type": "integer" + } + }, + "required": [ + "_id", + "amt", + "lbl", + "typ" + ] + } + }, + "dek": { + "type": "array", + "items": { + "type": "object", + "properties": { + "did": { + "type": "string" + }, + "lbl": { + "type": "string" + }, + "dir": { + "type": "integer" + }, + "aon": { + "type": "integer" + }, + "typ": { + "type": "integer" + }, + "dek": { + "type": "integer" + }, + "pub": { + "type": "integer" + }, + "dtl": { + "type": "string" + }, + "mux": { + "type": "integer" + }, + "itv": { + "type": "string" + }, + "lvl": { + "type": "array", + "items": { + "type": "string" + } + }, + "_id": { + "type": "string" + }, + "ctc": { + "type": "array", + "items": { + "type": "string" + } + }, + "tnm": { + "type": "string" + }, + "dnm": { + "type": "string" + }, + "cur": { + "type": "string" + }, + "_rt": { + "type": "integer" + }, + "bil": { + "type": "array", + "items": { + "type": "object", + "properties": { + "bid": { + "type": "string" + }, + "ttl": { + "type": "string" + }, + "way": { + "type": "integer" + }, + "amt": { + "type": "number" + }, + "itv": { + "type": "string" + }, + "mux": { + "type": "integer" + }, + "pub": { + "type": "integer" + }, + "dcc": { + "type": "object" + }, + "dso": { + "type": "string" + } + }, + "required": [ + "amt", + "bid", + "itv", + "mux", + "ttl", + "way" + ] + } + }, + "cat": { + "type": "integer" + }, + "put": { + "type": "array", + "items": { + "type": "string" + } + }, + "upg": { + "type": "array", + "items": { + "type": "string" + } + }, + "nyn": { + "type": "integer" + }, + "prp": { + "type": "integer" + }, + "psm": { + "type": "integer" + }, + "exp": { + "type": "string" + }, + "enr": { + "type": "array", + "items": { + "type": "object", + "properties": { + "day": { + "type": "integer" + }, + "ttl": { + "type": "string" + }, + "dtl": { + "type": "string" + } + }, + "required": [ + "day", + "dtl", + "ttl" + ] + } + }, + "wen": { + "type": "string" + }, + "pur": { + "type": "array", + "items": { + "type": "string" + } + }, + "puk": { + "type": "array", + "items": { + "type": "string" + } + }, + "puu": { + "type": "array", + "items": { + "type": "string" + } + }, + "pus": { + "type": "array", + "items": { + "type": "string" + } + }, + "pup": { + "type": "array", + "items": { + "type": "string" + } + }, + "puq": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "_id", + "dek", + "did", + "dir", + "lbl", + "pub", + "typ" + ] + } + }, + "SF": { + "type": "string" + }, + "_fd": { + "type": "integer" + }, + "_ts": { + "type": "integer" + }, + "_re": { + "type": "integer" + } + }, + "required": [ + "SF", + "_fd", + "_ts", + "adr", + "atg", + "cur", + "dcc", + "dek", + "eml", + "end", + "evg", + "nam", + "org", + "tpl", + "typ", + "uid" + ] +} diff --git a/memberPlumbing/tests/test_membershipworks.py b/memberPlumbing/tests/test_membershipworks.py new file mode 100644 index 0000000..0d4c0cb --- /dev/null +++ b/memberPlumbing/tests/test_membershipworks.py @@ -0,0 +1,179 @@ +import datetime +from unittest.mock import patch + +import pytest +import responses + +from ..MembershipWorks import BASE_URL, MembershipWorks, MembershipWorksRemoteError + + +@responses.activate +def test_login(): + membershipworks = MembershipWorks() + + responses.add( + responses.POST, BASE_URL + "usr", json={"SF": "test1", "org": 10000}, status=200 + ) + + membershipworks.login("test1@example.com", "test2") + + assert membershipworks.auth_token == "test1" + assert membershipworks.org_num == 10000 + + +@responses.activate +def test_login_fail(): + membershipworks = MembershipWorks() + + responses.add( + responses.POST, BASE_URL + "usr", json={"error": "Login/password not accepted"}, + ) + + with pytest.raises(MembershipWorksRemoteError) as e: + membershipworks.login("test1@example.com", "test2") + + assert ( + e.exception.args[0] + == "Error when attempting login: 200 OK\n" + + '{"error":"Login/password not accepted"}' + ) + + +@pytest.fixture +def membershipworks(): + mw = MembershipWorks() + + with responses.RequestsMock() as rsps: + rsps.add( + responses.POST, + BASE_URL + "usr", + json={ + "SF": "test1", + "org": 10000, + "dek": [{"_id": "12345", "lbl": "test", "dek": 1}], + }, + ) + + mw.login("test1@example.com", "test2") + + return mw + + +@responses.activate +def test_get_member_ids(membershipworks: MembershipWorks): + responses.add( + responses.GET, + BASE_URL + "ylp", + json={ + "typ": "a", + "usr": [{"uid": "asdf", "nam": "test", "rnk": 12.345}], + "_re": 631, + }, + ) + ids = membershipworks.get_member_ids(["test"]) + assert ids == ["asdf"] + + +@responses.activate +@patch("memberPlumbing.MembershipWorks.MembershipWorks.get_member_ids") +def test_get_members(mockIDs, membershipworks: MembershipWorks): + mockIDs.return_value = ["asdf"] + + responses.add( + responses.POST, + BASE_URL + "csv", + body=""""Account Name","First Name","Last Name","Account ID","Parent Account ID" +"bob whatever", "bob", "whatever", "aaaaaaaaaaaaaaaaaaaaaaaa", "" +"george whatever", "george", "whatever", "bbbbbbbbbbbbbbbbbbbbbbbb", "" +""", + ) + + members = membershipworks.get_members(["test"], "_id,nam") + + assert members == [ + { + "Account Name": "bob whatever", + "First Name": ' "bob"', + "Last Name": ' "whatever"', + "Account ID": ' "aaaaaaaaaaaaaaaaaaaaaaaa"', + "Parent Account ID": ' ""', + }, + { + "Account Name": "george whatever", + "First Name": ' "george"', + "Last Name": ' "whatever"', + "Account ID": ' "bbbbbbbbbbbbbbbbbbbbbbbb"', + "Parent Account ID": ' ""', + }, + ] + + +@responses.activate +def test_get_transactions_csv(membershipworks: MembershipWorks): + responses.add( + responses.GET, + BASE_URL + "csv", + body=""""Date","Name","Contact Person","Full Address","Street","City","State/Province","Postal Code","Country","Phone","Email","Membership Sub-Total","Event Sub-Total","Donation Sub-Total","Cart Sub-Total","Other Sub-Total","Handling","Total Tax","Transaction Total","Transaction Fee","Total Payment Due","Transaction Type","For","Items","Payment ID","Account ID","Discount Code","Note" +"Jan 01, 2020","whatever","","PO Box 0, fakeplace NH, US","PO Box 0","fakeplace","NH","","US","000-000-0000","example@example.com",1,0,0,0,0,0,0,1,0.32,0,"Membership","","CMS Membership on hold - Membership on hold","ch_aaaaaaaaaaaaaaaaaaaaaaaa","aaaaaaaaaaaaaaaaaaaaaaaa","","" +""", + ) + + transactions = membershipworks.get_transactions( + datetime.datetime(2000, 1, 1), datetime.datetime(2021, 1, 1) + ) + + assert transactions == [ + { + "Date": "Jan 01, 2020", + "Name": "whatever", + "Contact Person": "", + "Full Address": "PO Box 0, fakeplace NH, US", + "Street": "PO Box 0", + "City": "fakeplace", + "State/Province": "NH", + "Postal Code": "", + "Country": "US", + "Phone": "000-000-0000", + "Email": "example@example.com", + "Membership Sub-Total": "1", + "Event Sub-Total": "0", + "Donation Sub-Total": "0", + "Cart Sub-Total": "0", + "Other Sub-Total": "0", + "Handling": "0", + "Total Tax": "0", + "Transaction Total": "1", + "Transaction Fee": "0.32", + "Total Payment Due": "0", + "Transaction Type": "Membership", + "For": "", + "Items": "CMS Membership on hold - Membership on hold", + "Payment ID": "ch_aaaaaaaaaaaaaaaaaaaaaaaa", + "Account ID": "aaaaaaaaaaaaaaaaaaaaaaaa", + "Discount Code": "", + "Note": "", + } + ] + + +@responses.activate +def test_get_transactions_json(membershipworks: MembershipWorks): + data_json = [ + { + "cur": "usd", + "sid": "ch_aaaaaaaaaaaaaaaaaaaaaaaa", + "typ": 12, + "_dp": 1585100000, + "sum": 1, + "fee": 0.32, + "uid": "aaaaaaaaaaaaaaaaaaaaaaaa", + "nam": "whatever", + } + ] + responses.add(responses.GET, BASE_URL + "csv", json=data_json) + + transactions = membershipworks.get_transactions( + datetime.datetime(2000, 1, 1), datetime.datetime(2021, 1, 1), json=True + ) + assert "&txl=" in responses.calls[0].request.url # json flag + assert transactions == data_json diff --git a/memberPlumbing/tests/test_membershipworks_contract.py b/memberPlumbing/tests/test_membershipworks_contract.py new file mode 100644 index 0000000..17b0ef1 --- /dev/null +++ b/memberPlumbing/tests/test_membershipworks_contract.py @@ -0,0 +1,31 @@ +import json +import os + +import jsonschema +import requests + +import pytest + +from ..common import MEMBERSHIPWORKS_PASSWORD, MEMBERSHIPWORKS_USERNAME +from ..MembershipWorks import BASE_URL + +schemas_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "json-schemas") + + +def test_login(): + r = requests.post( + BASE_URL + "usr", + data={ + "_st": "all", + "eml": MEMBERSHIPWORKS_USERNAME, + "org": "10000", + "pwd": MEMBERSHIPWORKS_PASSWORD, + }, + ) + + with open(os.path.join(schemas_dir, "usr.schema")) as f: + schema = json.load(f) + + usr = r.json() + + jsonschema.validate(usr, schema)