Skip to content

Commit

Permalink
Added test-fraud command to unit-test.sh. Added setup.sh.
Browse files Browse the repository at this point in the history
  • Loading branch information
awltux committed Jan 17, 2023
1 parent b2cb326 commit bffa7b5
Show file tree
Hide file tree
Showing 12 changed files with 378 additions and 44 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ gnucash_uk_vat.egg-info/
auth.json
config.json
__pycache__
/test/gnucash-uk-vat-test.json
/setup.log
/test/user.json
4 changes: 2 additions & 2 deletions gnucash_uk_vat/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def write(self):

# Initialise configuration file with some (mainly) static values. Also,
# collate personal information for the Fraud API.
def initialise_config(config_file):
def initialise_config(config_file, profile_name):

# This gets hold of the MAC address, which the uuid module knows.
# FIXME: Hacky.
Expand Down Expand Up @@ -67,7 +67,7 @@ def initialise_config(config_file):
"bills": "Accounts Payable"
},
"application": {
"profile": "prod",
"profile": profile_name,
"client-id": "<CLIENT ID>",
"client-secret": "<SECRET>",
"terms-and-conditions-url": "http://example.com/terms_and_conditions/"
Expand Down
45 changes: 32 additions & 13 deletions gnucash_uk_vat/hmrc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from datetime import datetime, timedelta, date
import requests
import json
import hashlib

from . model import *

Expand Down Expand Up @@ -74,9 +75,10 @@ async def run(self):
class Vat:

# Constructor
def __init__(self, config, auth):
def __init__(self, config, auth, user):
self.config = config
self.auth = auth
self.user = user

# Production API endpoints
self.oauth_base = 'https://www.tax.service.gov.uk'
Expand All @@ -102,6 +104,12 @@ def get_auth_url(self):

return url + "?" + params

def get_auth_credentials(self):
auth_credentials = None
if self.user and self.user.get("userId") and self.user.get("password"):
auth_credentials = " UserId: %s\n Password: %s" % ( self.user.get("userId"), self.user.get("password") )
return auth_credentials

# Co-routine implementation
async def get_code_coro(self):

Expand All @@ -113,9 +121,13 @@ async def get_code_coro(self):

# Send user to the URL
url = self.get_auth_url()
auth_credentials = self.get_auth_credentials()

print("If you agree to the terms and conditions, visit the following URL and authenticate:")
print("If you agree to the terms and conditions, visit the following URL and authenticate:\n")
print(url)
if auth_credentials:
print("Authenticate using this test user:")
print("%s" % auth_credentials)

# Start auth code collector, and wait for it to finish
a = AuthCollector("localhost", 9876)
Expand Down Expand Up @@ -241,12 +253,18 @@ def build_fraud_headers(self):
if dev_id == "":
raise RuntimeError("identity.device.id not set")

product_name = self.config.get("application.product-name")
if product_name == "":
raise RuntimeError("application.product-name not set")

ua = urlencode({
"os-family": dev_os_fam,
"os-version": dev_os_version,
"device-manufacturer": dev_manuf,
"device-model": dev_model
})

hashed_license_id = hashlib.sha1(b'GPL3').hexdigest()

# Return headers
return {
Expand All @@ -259,14 +277,14 @@ def build_fraud_headers(self):
'Gov-Client-Local-IPs-Timestamp': self.config.get("identity.time"),
'Gov-Client-MAC-Addresses': mac,
'Gov-Client-User-Agent': ua,
'Gov-Vendor-Version': 'gnucash-uk-vat=1.0',
'Gov-Vendor-Product-Name': 'gnucash-uk-vat',
'Gov-Vendor-Version': '%s=1.0' % product_name,
'Gov-Vendor-Product-Name': '%s' % product_name,
'Gov-Vendor-License-Ids': '%s=%s' % (product_name, hashed_license_id ),
'Authorization': 'Bearer %s' % self.auth.get("access_token"),
}

# Test fraud headers. Only available in Sandbox, not production
async def test_fraud_headers(self):

headers = self.build_fraud_headers()
headers['Accept'] = 'application/vnd.hmrc.1.0+json'

Expand Down Expand Up @@ -476,30 +494,31 @@ async def get_vat_payments(self, vrn, start, end):

# Like VAT, but talks to test API endpoints.
class VatTest(Vat):
def __init__(self, config, auth):
super().__init__(config, auth)
def __init__(self, config, auth, user):

super().__init__(config, auth, user)
self.oauth_base = 'https://test-www.tax.service.gov.uk'
self.api_base = 'https://test-api.service.hmrc.gov.uk'

# Like VAT, but talks to an API endpoints on localhost:8080.
class VatLocalTest(Vat):
def __init__(self, config, auth):
super().__init__(config, auth)
def __init__(self, config, auth, user):
super().__init__(config, auth, user)
self.oauth_base = 'http://localhost:8080'
self.api_base = 'http://localhost:8080'

def create(config, auth):
def create(config, auth, user):

# Get profile
prof = config.get("application.profile")

# Initialise API client endpoint based on selected profile
if prof == "prod":
return Vat(config, auth)
return Vat(config, auth, user)
elif prof == "test":
return VatTest(config, auth)
return VatTest(config, auth, user)
elif prof == "local":
return VatLocalTest(config, auth)
return VatLocalTest(config, auth, user)
else:
raise RuntimeError("Profile '%s' is not known." % prof)

23 changes: 18 additions & 5 deletions scripts/gnucash-uk-vat
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,20 @@ default_end = str(datetime.utcnow().date())

# Command-line argument parser
parser = argparse.ArgumentParser(description="Gnucash to HMRC VAT API")
parser.add_argument('--userfile', '-u',
default='user.json',
help='MTD test user file returned by ./get-test-user (default: user.json)')
parser.add_argument('--gnucash', '-g',
default='accounts.sqlite3.gnucash',
help='Target profile (default: test)')
parser.add_argument('--profile', '-p',
default='test',
help='Target profile (default: test)')
parser.add_argument('--config', '-c',
default='config.json',
default='config.json',
help='Configuration file (default: config.json)')
parser.add_argument('--auth', '-a',
default='auth.json',
default='auth.json',
help='File to store auth credentials (default: auth.json)')
parser.add_argument('--init-config', action='store_true',
help='Initialise configuration file with template')
Expand Down Expand Up @@ -76,16 +85,20 @@ async def run():
# Initialise configuration operation. This goes here as configuration
# can be initialised if no auth has been performed
if args.init_config:
initialise_config(args.config)
initialise_config(args.config, args.profile, args.gnucash)
sys.exit(0)

# Initialise config and auth.
config = Config(args.config)
auth = Auth(args.auth)
if os.path.exists(args.userfile):
user = Config(args.userfile)
else:
user = None

h = hmrc.create(config, auth)
h = hmrc.create(config, auth, user)

# Authentication operation goes here.
# Authenticate HMRC [test]user with MTD API.
if args.authenticate:
await authenticate(h, auth)
sys.exit(0)
Expand Down
16 changes: 16 additions & 0 deletions setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash

# HACKY WINDOWS GIT BASH CONSOLE ONLY: Check for Admin privs using the help output from an admin-only windows command
if [[ $(sfc 2>&1 | tr -d '\0') =~ SCANNOW ]]; then
if [[ -f $(dirname $(which python))/scripts/gnucash-uk-vat ]]; then
echo "Update gnucash-uk-vat"
# python -m pip install --upgrade --force-reinstall . 2>&1 | tee setup.log
python -m pip install --upgrade . 2>&1 | tee setup.log
else
echo "Install gnucash-uk-vat"
python -m pip install . 2>&1 | tee setup.log
fi
else
echo "ERROR: setup.sh must be run in a console with Administrator priviledges"
exit 1
fi
19 changes: 0 additions & 19 deletions test-fraud-api

This file was deleted.

12 changes: 12 additions & 0 deletions test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Test Suite to run tests against HMRC sandbox.


Open a console (Git Bash for Windows) in the 'test' directory.

# Help
Display the help using:
./unit-test.sh help

Create a config file to allow access to HMRC.
./unit-test.sh config

37 changes: 37 additions & 0 deletions test/config.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"accounts": {
"kind": "piecash",
"file": "hmrc-test.sqlite3.gnucash",
"vatDueSales": "Retained Earnings Transfer Account:[AC435=AC36] :[AC32=CP44] :[AC26] :[AC16=CP14=CP39] :[AC12=CP7] :[email protected]%:0000-05",
"vatDueAcquisitions": "Retained Earnings Transfer Account:[AC435=AC36] :[AC32=CP44] :[AC26] :[AC16=CP14=CP39] :[AC12=CP7] :ZeroVatAccount",
"totalVatDue": "Retained Earnings Transfer Account:[AC435=AC36] :[AC32=CP44] :[AC26] :[AC16=CP14=CP39] :[AC12=CP7] :[email protected]%:0000-05",
"vatReclaimedCurrPeriod": "Retained Earnings Transfer Account:[AC435=AC36] :[AC32=CP44] :[AC26] :[AC16=CP14=CP39] :[AC12=CP7] :ZeroVatAccount",
"netVatDue": "Retained Earnings Transfer Account:[AC435=AC36] :[AC32=CP44] :[AC26] :[AC16=CP14=CP39] :[AC12=CP7] :[email protected]%:0000-05",
"totalValueSalesExVAT": "Retained Earnings Transfer Account:[AC435=AC36] :[AC32=CP44] :[AC26] :[AC16=CP14=CP39] :[AC12=CP7] :FRV Turnover:0000-05",
"totalValuePurchasesExVAT": "Retained Earnings Transfer Account:[AC435=AC36] :[AC32=CP44] :[AC26] :[AC16=CP14=CP39] :[AC12=CP7] :ZeroVatAccount",
"totalValueGoodsSuppliedExVAT": "Retained Earnings Transfer Account:[AC435=AC36] :[AC32=CP44] :[AC26] :[AC16=CP14=CP39] :[AC12=CP7] :ZeroVatAccount",
"totalAcquisitionsExVAT": "Retained Earnings Transfer Account:[AC435=AC36] :[AC32=CP44] :[AC26] :[AC16=CP14=CP39] :[AC12=CP7] :ZeroVatAccount",
"liabilities": "Retained Earnings Transfer Account:[AC435=AC36] :[AC32=CP44] :[AC26] :[AC16=CP14=CP39] :[AC12=CP7] :ZeroVatAccount",
"bills": "Retained Earnings Transfer Account:[AC435=AC36] :[AC32=CP44] :[AC26] :[AC16=CP14=CP39] :[AC12=CP7] :ZeroVatAccount"
},
"application": {
"profile": "test",
"client-id": "<CLIENT_ID>",
"client-secret": "<CLIENT-SECRET>",
"terms-and-conditions-url": "http://example.com/terms_and_conditions/"
},
"identity": {
"vrn": "<VRN>",
"device": {
"os-family": "Windows",
"os-version": "8.1",
"device-manufacturer": "ASUSTeK COMPUTER INC.",
"device-model": "G751JY",
"id": "12345678-1234-1234-1234-1234567890"
},
"user": "account_name",
"local-ip": "192.168.0.100",
"mac-address": "12:34:56:78:90:12",
"time": "2023-01-16T16:33:01.953Z"
}
}
File renamed without changes.
28 changes: 23 additions & 5 deletions hmrc-tools/get-test-user → test/get-test-user
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,25 @@
import requests
import json
import sys
import argparse

# Add your client ID and secret here
client_id = "CLIENT_ID"
client_secret = "CLIENT_SECRET"
from gnucash_uk_vat.config import *

# Command-line argument parser
parser = argparse.ArgumentParser(description="Gnucash to HMRC VAT API")
parser.add_argument('--config', '-c',
default='gnucash-uk-vat-test.json',
help='Configuration file (default: gnucash-uk-vat-test.json)')
parser.add_argument('--userfile', '-u',
default='user.json',
help='MTD User file (default: user.json)')

# Parse arguments
args = parser.parse_args(sys.argv[1:])
config = Config(args.config)

client_id = config.get("application.client-id")
client_secret = config.get("application.client-secret")

headers = {
"content-type": "application/x-www-form-urlencoded",
Expand Down Expand Up @@ -52,8 +67,6 @@ if resp.status_code != 201:
print(resp.text)
sys.exit(1)

#print(json.dumps(resp.json(), indent=4))

user = resp.json()

def present(x):
Expand All @@ -63,3 +76,8 @@ print("User: ", present(user["userId"]))
print("Password: ", present(user["password"]))
print("VRN: ", present(user["vrn"]))

with open(args.userfile, "w") as user_file:
user_file.write(json.dumps(user, indent=4))



62 changes: 62 additions & 0 deletions test/test-fraud-api
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env python3

# This calls the Fraud API test endpoint on sandbox. Only useful for developers.
import asyncio
import sys
import types
import json
import argparse

import gnucash_uk_vat.hmrc as hmrc
from gnucash_uk_vat.config import Config
from gnucash_uk_vat.auth import Auth

# Command-line argument parser
parser = argparse.ArgumentParser(description="Gnucash to HMRC VAT API")
parser.add_argument('--config', '-c',
default='gnucash-uk-vat-test.json',
help='Configuration file (default: gnucash-uk-vat-test.json)')

# Parse arguments
args = parser.parse_args(sys.argv[1:])


# Loads default config, user and auth
config = Config(args.config)
u = Config()
a = Auth()

# Use the sandbox service
svc = hmrc.VatTest(config, a, u)

# Call fraud API and dump out results. This is gonna be a fail on lack of
# gov-vendor-license-ids and gov-lient-multi-factor headers.

def asyncrun(coro):
# Prevent "RuntimeError: Event loop is closed" on Windows
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

# asyncio.run introduced in Python 3.7, just use that.
if sys.version_info >= (3, 7):
return asyncio.run(coro)

# Emulate asyncio.run()

# asyncio.run() requires a coro, so require it here as well
if not isinstance(coro, types.CoroutineType):
raise TypeError("run() requires a coroutine object")

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
return loop.run_until_complete(coro)
finally:
loop.close()
asyncio.set_event_loop(None)

try:
resp = asyncrun(svc.test_fraud_headers())
print(json.dumps(resp, indent=4))
except Exception as e:
sys.stderr.write("Exception: %s\n" % e)
sys.exit(1)
Loading

0 comments on commit bffa7b5

Please sign in to comment.