Added tests to cover most of the use cases and code, and a bunch of stuff to make the mocks happy

This commit is contained in:
Kelly McDonald 2020-12-14 10:27:40 -05:00
parent adc4dc4453
commit ee3ee9fd4a
5 changed files with 392 additions and 5 deletions

View File

@ -97,7 +97,7 @@ def sync_all_changed_workflows(remote):
def sync_changed_files(remote,workflow_spec_id): def sync_changed_files(remote,workflow_spec_id):
# make sure that spec is local before syncing files # make sure that spec is local before syncing files
specdict = WorkflowSyncService.get_remote_workfow_spec(remote,workflow_spec_id) specdict = WorkflowSyncService.get_remote_workflow_spec(remote,workflow_spec_id)
localspec = session.query(WorkflowSpecModel).filter(WorkflowSpecModel.id == workflow_spec_id).first() localspec = session.query(WorkflowSpecModel).filter(WorkflowSpecModel.id == workflow_spec_id).first()
if localspec is None: if localspec is None:
@ -173,6 +173,8 @@ def get_changed_files(remote,workflow_spec_id,as_df=False):
# get the local thumbprints & make sure that 'workflow_spec_id' is a column, not an index # get the local thumbprints & make sure that 'workflow_spec_id' is a column, not an index
local = get_workflow_spec_files_dataframe(workflow_spec_id).reset_index() local = get_workflow_spec_files_dataframe(workflow_spec_id).reset_index()
local['md5_hash'] = local['md5_hash'].astype('str') local['md5_hash'] = local['md5_hash'].astype('str')
remote_files['md5_hash'] = remote_files['md5_hash'].astype('str')
different = remote_files.merge(local, different = remote_files.merge(local,
right_on=['filename','md5_hash'], right_on=['filename','md5_hash'],
left_on=['filename','md5_hash'], left_on=['filename','md5_hash'],
@ -205,7 +207,7 @@ def get_changed_files(remote,workflow_spec_id,as_df=False):
# get an exclusive or list of workflow ids - that is we want lists of files that are # get an exclusive or list of workflow ids - that is we want lists of files that are
# on one machine or the other, but not both # on one machine or the other, but not both
remote_spec_ids = remote[['filename']] remote_spec_ids = remote_files[['filename']]
local_spec_ids = local[['filename']] local_spec_ids = local[['filename']]
left = remote_spec_ids[~remote_spec_ids['filename'].isin(local_spec_ids['filename'])] left = remote_spec_ids[~remote_spec_ids['filename'].isin(local_spec_ids['filename'])]
right = local_spec_ids[~local_spec_ids['filename'].isin(remote_spec_ids['filename'])] right = local_spec_ids[~local_spec_ids['filename'].isin(remote_spec_ids['filename'])]

View File

@ -207,7 +207,7 @@ class BaseTest(unittest.TestCase):
@staticmethod @staticmethod
def workflow_sync_response(file_name): def workflow_sync_response(file_name):
filepath = os.path.join(app.root_path, '..', 'tests', 'data', 'workflow_sync_responses', file_name) filepath = os.path.join(app.root_path, '..', 'tests', 'data', 'workflow_sync_responses', file_name)
with open(filepath, 'r') as myfile: with open(filepath, 'rb') as myfile:
data = myfile.read() data = myfile.read()
return data return data

View File

@ -0,0 +1,200 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1gjhqt9" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.0">
<bpmn:process id="Process_SecondFact" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_0c7wlth</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:userTask id="Task_User_Select_Type" name="Set Type" camunda:formKey="Get A Random Fun Fact">
<bpmn:documentation># h1 Heading 8-)
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
## Horizontal Rules
___
---
***
## Typographic replacements
"double quotes" and 'single quotes'
## Emphasis
**This is bold text**
__This is bold text__
*This is italic text*
_This is italic text_
~~Strikethrough~~
## Blockquotes
&gt; Blockquotes can also be nested...
&gt;&gt; ...by using additional greater-than signs right next to each other...
&gt; &gt; &gt; ...or with spaces between arrows.
## Lists
Unordered
+ Create a list by starting a line with `+`, `-`, or `*`
+ Sub-lists are made by indenting 2 spaces:
- Marker character change forces new list start:
* Ac tristique libero volutpat at
+ Facilisis in pretium nisl aliquet
- Nulla volutpat aliquam velit
+ Very easy!
Ordered
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
1. You can use sequential numbers...
1. ...or keep all the numbers as `1.`
Start numbering with offset:
57. foo
1. bar
## Tables
| Option | Description |
| ------ | ----------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
Right aligned columns
| Option | Description |
| ------:| -----------:|
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
## Links
[link text](http://dev.nodeca.com)
[link with title](http://nodeca.github.io/pica/demo/ "title text!")
Autoconverted link https://github.com/nodeca/pica (enable linkify to see)
## Images
![Minion](https://octodex.github.com/images/minion.png)
![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")</bpmn:documentation>
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="type" label="Type" type="enum" defaultValue="cat">
<camunda:validation>
<camunda:constraint name="required" config="true" />
</camunda:validation>
<camunda:value id="norris" name="Chuck Norris" />
<camunda:value id="cat" name="Cat Fact" />
<camunda:value id="buzzword" name="Business Buzzword" />
</camunda:formField>
</camunda:formData>
<camunda:properties>
<camunda:property name="type" value="string" />
</camunda:properties>
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_0c7wlth</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0641sh6</bpmn:outgoing>
</bpmn:userTask>
<bpmn:scriptTask id="Task_Get_Fact_From_API" name="Display Fact">
<bpmn:documentation />
<bpmn:extensionElements>
<camunda:inputOutput>
<camunda:inputParameter name="Fact.type" />
</camunda:inputOutput>
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_0641sh6</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0t29gjo</bpmn:outgoing>
<bpmn:script>FactService = fact_service()</bpmn:script>
</bpmn:scriptTask>
<bpmn:endEvent id="EndEvent_0u1cgrf">
<bpmn:documentation># Great Job!
You have completed the random fact generator.
You chose to receive a random fact of the type: "{{type}}"
Your random fact is:
{{details}}</bpmn:documentation>
<bpmn:incoming>SequenceFlow_0t29gjo</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_0c7wlth" sourceRef="StartEvent_1" targetRef="Task_User_Select_Type" />
<bpmn:sequenceFlow id="SequenceFlow_0641sh6" sourceRef="Task_User_Select_Type" targetRef="Task_Get_Fact_From_API" />
<bpmn:sequenceFlow id="SequenceFlow_0t29gjo" sourceRef="Task_Get_Fact_From_API" targetRef="EndEvent_0u1cgrf" />
<bpmn:textAnnotation id="TextAnnotation_09fq7kh">
<bpmn:text>User sets the Fact.type to cat, norris, or buzzword</bpmn:text>
</bpmn:textAnnotation>
<bpmn:association id="Association_1cfasjp" sourceRef="Task_User_Select_Type" targetRef="TextAnnotation_09fq7kh" />
<bpmn:textAnnotation id="TextAnnotation_1234e5n">
<bpmn:text>Makes an API  call to get a fact of the required type.</bpmn:text>
</bpmn:textAnnotation>
<bpmn:association id="Association_1qirnyy" sourceRef="Task_Get_Fact_From_API" targetRef="TextAnnotation_1234e5n" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1ds61df">
<bpmndi:BPMNEdge id="SequenceFlow_0t29gjo_di" bpmnElement="SequenceFlow_0t29gjo">
<di:waypoint x="570" y="250" />
<di:waypoint x="692" y="250" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_0641sh6_di" bpmnElement="SequenceFlow_0641sh6">
<di:waypoint x="370" y="250" />
<di:waypoint x="470" y="250" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_0c7wlth_di" bpmnElement="SequenceFlow_0c7wlth">
<di:waypoint x="188" y="250" />
<di:waypoint x="270" y="250" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="152" y="232" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="UserTask_186s7tw_di" bpmnElement="Task_User_Select_Type">
<dc:Bounds x="270" y="210" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_0u1cgrf_di" bpmnElement="EndEvent_0u1cgrf">
<dc:Bounds x="692" y="232" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="TextAnnotation_09fq7kh_di" bpmnElement="TextAnnotation_09fq7kh">
<dc:Bounds x="330" y="116" width="99.99202297383536" height="68.28334396936822" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="TextAnnotation_1234e5n_di" bpmnElement="TextAnnotation_1234e5n">
<dc:Bounds x="570" y="120" width="100" height="68" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="ScriptTask_10keafb_di" bpmnElement="Task_Get_Fact_From_API">
<dc:Bounds x="470" y="210" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Association_1cfasjp_di" bpmnElement="Association_1cfasjp">
<di:waypoint x="344" y="210" />
<di:waypoint x="359" y="184" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Association_1qirnyy_di" bpmnElement="Association_1qirnyy">
<di:waypoint x="561" y="210" />
<di:waypoint x="584" y="188" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1gjhqt9" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.0">
<bpmn:process id="Process_SecondFact" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_0c7wlth</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:userTask id="Task_User_Select_Type" name="Set Type" camunda:formKey="Get A Random Fun Fact">
<bpmn:documentation># h1 Heading 8-)
NEW_FILE_ADDED
![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")</bpmn:documentation>
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="type" label="Type" type="enum" defaultValue="cat">
<camunda:validation>
<camunda:constraint name="required" config="true" />
</camunda:validation>
<camunda:value id="norris" name="Chuck Norris" />
<camunda:value id="cat" name="Cat Fact" />
<camunda:value id="buzzword" name="Business Buzzword" />
</camunda:formField>
</camunda:formData>
<camunda:properties>
<camunda:property name="type" value="string" />
</camunda:properties>
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_0c7wlth</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0641sh6</bpmn:outgoing>
</bpmn:userTask>
<bpmn:scriptTask id="Task_Get_Fact_From_API" name="Display Fact">
<bpmn:documentation />
<bpmn:extensionElements>
<camunda:inputOutput>
<camunda:inputParameter name="Fact.type" />
</camunda:inputOutput>
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_0641sh6</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0t29gjo</bpmn:outgoing>
<bpmn:script>FactService = fact_service()</bpmn:script>
</bpmn:scriptTask>
<bpmn:endEvent id="EndEvent_0u1cgrf">
<bpmn:documentation># Great Job!
You have completed the random fact generator.
You chose to receive a random fact of the type: "{{type}}"
Your random fact is:
{{details}}</bpmn:documentation>
<bpmn:incoming>SequenceFlow_0t29gjo</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_0c7wlth" sourceRef="StartEvent_1" targetRef="Task_User_Select_Type" />
<bpmn:sequenceFlow id="SequenceFlow_0641sh6" sourceRef="Task_User_Select_Type" targetRef="Task_Get_Fact_From_API" />
<bpmn:sequenceFlow id="SequenceFlow_0t29gjo" sourceRef="Task_Get_Fact_From_API" targetRef="EndEvent_0u1cgrf" />
<bpmn:textAnnotation id="TextAnnotation_09fq7kh">
<bpmn:text>User sets the Fact.type to cat, norris, or buzzword</bpmn:text>
</bpmn:textAnnotation>
<bpmn:association id="Association_1cfasjp" sourceRef="Task_User_Select_Type" targetRef="TextAnnotation_09fq7kh" />
<bpmn:textAnnotation id="TextAnnotation_1234e5n">
<bpmn:text>Makes an API  call to get a fact of the required type.</bpmn:text>
</bpmn:textAnnotation>
<bpmn:association id="Association_1qirnyy" sourceRef="Task_Get_Fact_From_API" targetRef="TextAnnotation_1234e5n" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1ds61df">
<bpmndi:BPMNEdge id="SequenceFlow_0t29gjo_di" bpmnElement="SequenceFlow_0t29gjo">
<di:waypoint x="570" y="250" />
<di:waypoint x="692" y="250" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_0641sh6_di" bpmnElement="SequenceFlow_0641sh6">
<di:waypoint x="370" y="250" />
<di:waypoint x="470" y="250" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_0c7wlth_di" bpmnElement="SequenceFlow_0c7wlth">
<di:waypoint x="188" y="250" />
<di:waypoint x="270" y="250" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="152" y="232" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="UserTask_186s7tw_di" bpmnElement="Task_User_Select_Type">
<dc:Bounds x="270" y="210" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_0u1cgrf_di" bpmnElement="EndEvent_0u1cgrf">
<dc:Bounds x="692" y="232" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="TextAnnotation_09fq7kh_di" bpmnElement="TextAnnotation_09fq7kh">
<dc:Bounds x="330" y="116" width="99.99202297383536" height="68.28334396936822" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="TextAnnotation_1234e5n_di" bpmnElement="TextAnnotation_1234e5n">
<dc:Bounds x="570" y="120" width="100" height="68" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="ScriptTask_10keafb_di" bpmnElement="Task_Get_Fact_From_API">
<dc:Bounds x="470" y="210" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Association_1cfasjp_di" bpmnElement="Association_1cfasjp">
<di:waypoint x="344" y="210" />
<di:waypoint x="359" y="184" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Association_1qirnyy_di" bpmnElement="Association_1qirnyy">
<di:waypoint x="561" y="210" />
<di:waypoint x="584" y="188" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -2,12 +2,18 @@ from unittest.mock import patch
from crc import db from crc import db
from tests.base_test import BaseTest from tests.base_test import BaseTest
from crc.api.workflow_sync import get_all_spec_state, get_changed_workflows from crc.api.workflow_sync import get_all_spec_state, \
get_changed_workflows, \
get_workflow_spec_files, \
get_changed_files, \
get_workflow_specification, \
sync_changed_files
from crc.models.workflow import WorkflowSpecModel from crc.models.workflow import WorkflowSpecModel
import json
from datetime import datetime from datetime import datetime
from crc.services.file_service import FileService from crc.services.file_service import FileService
class TestWorkflowSync(BaseTest): class TestWorkflowSync(BaseTest):
@patch('crc.services.workflow_sync.WorkflowSyncService.get_all_remote_workflows') @patch('crc.services.workflow_sync.WorkflowSyncService.get_all_remote_workflows')
@ -73,3 +79,78 @@ class TestWorkflowSync(BaseTest):
response = get_changed_workflows('localhost:0000') #endpoint is not used due to mock response = get_changed_workflows('localhost:0000') #endpoint is not used due to mock
self.assertIsNotNone(response) self.assertIsNotNone(response)
self.assertEqual(response,[]) self.assertEqual(response,[])
@patch('crc.services.workflow_sync.WorkflowSyncService.get_remote_workflow_spec_files')
def test_file_differences(self, mock_get):
self.load_example_data()
othersys = get_workflow_spec_files('random_fact')
othersys[1]['date_created'] = str(datetime.now())
othersys[1]['md5_hash'] = '12345'
mock_get.return_value = othersys
response = get_changed_files('localhost:0000','random_fact',as_df=False) #endpoint is not used due to mock
self.assertIsNotNone(response)
self.assertEqual(len(response),1)
self.assertEqual(response[0]['filename'], 'random_fact2.bpmn')
self.assertEqual(response[0]['location'], 'remote')
self.assertEqual(response[0]['new'], False)
@patch('crc.services.workflow_sync.WorkflowSyncService.get_remote_workflow_spec_files')
def test_file_differences(self, mock_get):
self.load_example_data()
othersys = get_workflow_spec_files('random_fact')
othersys[1]['date_created'] = str(datetime.now())
othersys[1]['md5_hash'] = '12345'
mock_get.return_value = othersys
response = get_changed_files('localhost:0000','random_fact',as_df=False) #endpoint is not used due to mock
self.assertIsNotNone(response)
self.assertEqual(len(response),1)
self.assertEqual(response[0]['filename'], 'random_fact2.bpmn')
self.assertEqual(response[0]['location'], 'remote')
self.assertEqual(response[0]['new'], False)
@patch('crc.services.workflow_sync.WorkflowSyncService.get_remote_file_by_hash')
@patch('crc.services.workflow_sync.WorkflowSyncService.get_remote_workflow_spec_files')
@patch('crc.services.workflow_sync.WorkflowSyncService.get_remote_workflow_spec')
def test_file_differences(self, workflow_mock, spec_files_mock, file_data_mock):
self.load_example_data()
remote_workflow = get_workflow_specification('random_fact')
self.assertEqual(remote_workflow['display_name'],'Random Fact')
remote_workflow['description'] = 'This Workflow came from Remote'
remote_workflow['display_name'] = 'Remote Workflow'
workflow_mock.return_value = remote_workflow
othersys = get_workflow_spec_files('random_fact')
othersys[1]['date_created'] = str(datetime.now())
othersys[1]['md5_hash'] = '12345'
spec_files_mock.return_value = othersys
file_data_mock.return_value = self.workflow_sync_response('random_fact2.bpmn')
response = sync_changed_files('localhost:0000','random_fact') # endpoint not used due to mock
self.assertIsNotNone(response)
self.assertEqual(len(response),1)
self.assertEqual(response[0], 'random_fact2.bpmn')
files = FileService.get_spec_data_files('random_fact')
md5sums = [str(f.md5_hash) for f in files]
self.assertEqual('21bb6f9e-0af7-0ab2-0fc7-ec0f94787e58' in md5sums, True)
new_local_workflow = get_workflow_specification('random_fact')
self.assertEqual(new_local_workflow['display_name'],'Remote Workflow')
@patch('crc.services.workflow_sync.WorkflowSyncService.get_remote_workflow_spec_files')
@patch('crc.services.workflow_sync.WorkflowSyncService.get_remote_workflow_spec')
def test_file_deleted(self, workflow_mock, spec_files_mock):
self.load_example_data()
remote_workflow = get_workflow_specification('random_fact')
workflow_mock.return_value = remote_workflow
othersys = get_workflow_spec_files('random_fact')
del(othersys[1])
spec_files_mock.return_value = othersys
response = sync_changed_files('localhost:0000','random_fact') # endpoint not used due to mock
self.assertIsNotNone(response)
# when we delete a local file, we do not return that it was deleted - just
# a list of updated files. We may want to change this in the future.
self.assertEqual(len(response),0)
files = FileService.get_spec_data_files('random_fact')
self.assertEqual(len(files),1)