Skip to content

Commit a7fc837

Browse files
authored
Merge pull request #3 from jn9e9/simple-mock
initial version of mock service.
2 parents 2b771aa + 3b86ec0 commit a7fc837

File tree

4 files changed

+160
-3
lines changed

4 files changed

+160
-3
lines changed

README.md

+21-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,19 @@ pip install -r requirements.txt
1515
```
1616
# Usage
1717
## Run Mock Service
18-
**TBD**
18+
19+
Currently the mock service only supports running test cases from a folder in a request matching mode. To run the service:
20+
21+
```bash
22+
# To get help
23+
python parsec-mock.py --help
24+
# To define test case folder (defaults to ./testdata)
25+
python parsec-mock.py --test-folder FOLDER_NAME
26+
# To define the unix socket address to listen to. Defaults to ./parsec.sock
27+
python parsec-mock.py --parsec-socket SOCKET_ADDRESS
28+
```
29+
30+
In request matching mode, the mock service loads all the test cases from the folder and, if it receives a request that matches the (base 64 decoded) test_data.request field in the test case file, it will repond with the base 64 decoded data in the test_data.response field. If more than one test case shares the same test_data.request value, then the last one to be loaded will be used.
1931

2032
## Generate Test Data
2133
To generate test data from test specs, run
@@ -157,7 +169,15 @@ The whole test spec is defined in the spec: object in the file. In that spec, t
157169
| response.auth.type | This can either be ```none```, which will cause the auth section of the message to be empty (equivalent of the No Authentication type of authentication); or it can be ```direct```, which will cause the auth section of the message to contain authentication data corresponding to the format for Direct Authentication. If ```direct``` is set, then the result.auth.app_name field must be set. Note that this field does not cause the header auth_type field to be set. **NOTE:** A Parsec client would not send authentication data in a result message, but this spec format allows test authors to create the message as they wish to excersice the parsec client code. |
158170
| response.auth.app_name | Used to populate the auth section of the message when ```direct``` authentication is selected |
159171

172+
# Mock service testing
173+
174+
There is a very simple test for the mock service in the test folder. Run this from the current folder:
175+
176+
```bash
177+
python ./test/mock_test.py
178+
```
160179

180+
This expects the mock service to be listening on ./parsec.sock and to have the testdata/ping_no_auth.test.yaml test spec loaded
161181

162182
# License
163183

parsec-mock.py

+92
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,94 @@
11
# Copyright 2021 Contributors to the Parsec project.
22
# SPDX-License-Identifier: Apache-2.0
3+
import click
4+
5+
import socket
6+
import os
7+
from os import listdir
8+
from os.path import isfile, join
9+
10+
import base64
11+
12+
from yaml import safe_load
13+
14+
15+
@click.command()
16+
@click.option('--test-folder', default='./testdata', help='Load all tests from folder')
17+
@click.option('--parsec-socket', default='./parsec.sock', help='Path to parsec unix socket')
18+
def run_test(test_folder, parsec_socket):
19+
print('Mock parsec service listening on unix://{}.'.format(parsec_socket))
20+
21+
test_cases = load_tests_from_folder(test_folder)
22+
23+
print('Serving all {} tests in folder {}'.format(len(test_cases),test_folder))
24+
25+
# Make sure socket doesn't already exist
26+
try:
27+
os.unlink(parsec_socket)
28+
except OSError:
29+
if os.path.exists(parsec_socket):
30+
print('Error removing old parsec socket, exiting')
31+
return
32+
33+
# Create a unix socket
34+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
35+
sock.bind(parsec_socket)
36+
37+
sock.listen(1)
38+
39+
while True:
40+
connection, client_addr = sock.accept()
41+
try:
42+
print('Connection received from {}'.format(client_addr))
43+
received_data = connection.recv(4096)
44+
b64_received_data = base64.b64encode(received_data).decode('ascii')
45+
if b64_received_data in test_cases:
46+
test_case = test_cases[b64_received_data]
47+
print('Received expected request for test case {}'.format(test_case.spec.name))
48+
bin_response = base64.b64decode(test_case.test_data.response)
49+
connection.sendall(bin_response)
50+
else:
51+
print('Received unexpected request {}'.format(b64_received_data))
52+
finally:
53+
connection.close()
54+
55+
56+
class TestSpec(object):
57+
"""Class to represent a test specification. Used to convert
58+
dictionary created by pyaml to object format, making code easier to read."""
59+
60+
def __init__(self, dictionary):
61+
def _traverse(key, element):
62+
if isinstance(element, dict):
63+
return key, TestSpec(element)
64+
else:
65+
return key, element
66+
67+
objd = dict(_traverse(k, v) for k, v in dictionary.items())
68+
self.__dict__.update(objd)
69+
self.basedict = dictionary
70+
71+
def is_valid(self):
72+
return True
73+
74+
75+
def load_tests_from_folder(test_folder):
76+
tests = {}
77+
"""Read test specs from a folder"""
78+
specfiles = [f for f in listdir(test_folder) if isfile(join(test_folder, f))]
79+
80+
for file in specfiles:
81+
print(f"Parsing spec file: {file}")
82+
with open(os.path.join(test_folder, file), 'r') as f:
83+
spec = safe_load(f)
84+
testspec = TestSpec(spec)
85+
if testspec.is_valid():
86+
tests[testspec.test_data.request] = testspec
87+
else:
88+
print(f"Error loading test spec from {file}")
89+
90+
return tests
91+
92+
93+
if __name__ == "__main__":
94+
run_test()

requirements.txt

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
1-
# Copyright 2021 Contributors to the Parsec project.
2-
# SPDX-License-Identifier: Apache-2.0
1+
attrs==20.3.0
2+
click==7.1.2
3+
flake8==3.9.0
4+
iniconfig==1.1.1
5+
mccabe==0.6.1
6+
packaging==20.9
7+
pluggy==0.13.1
38
protobuf==3.15.6
9+
py==1.10.0
410
pyaml==20.4.0
11+
pycodestyle==2.7.0
12+
pyflakes==2.3.1
13+
pyparsing==2.4.7
14+
pytest==6.2.2
515
PyYAML==5.4.1
16+
rope==0.18.0
617
six==1.15.0
18+
toml==0.10.2

test/mock_test.py

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import socket
2+
3+
import base64
4+
5+
# Create a UDS socket
6+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
7+
8+
# Connect the socket to the port where the server is listening
9+
server_address = './parsec.sock'
10+
print('connecting to %s' % server_address)
11+
12+
sock.connect(server_address)
13+
14+
try:
15+
# Send data
16+
message = 'EKfAXh4AAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAA'
17+
bin_message = base64.b64decode(message)
18+
19+
expected_response = 'EKfAXh4AAQAAAAAAAAAAAAAAAAAAAAIAAAAAAAEAAAAAAAAACAE='
20+
bin_expected = base64.b64decode(expected_response)
21+
22+
print('sending "%s"' % message)
23+
sock.sendall(bin_message)
24+
25+
received_data = sock.recv(4096)
26+
print('received {}'.format(base64.b64encode(received_data).decode('ascii')))
27+
if bin_expected == received_data:
28+
print('Received expected response')
29+
else:
30+
print('Did not get expected response')
31+
finally:
32+
print('closing socket')
33+
sock.close()

0 commit comments

Comments
 (0)