first commit

This commit is contained in:
Senad Uka
2017-11-07 09:23:57 +01:00
commit 0eee92660a
356 changed files with 747259 additions and 0 deletions

View File

View File

@@ -0,0 +1,121 @@
from flask.ext.testing import LiveServerTestCase
import mockredis
from splinter import Browser
from helix import main
from helix.constants import sql_constant
from helix.constants.module_type import ModuleType
from helix.constants.system_type import SystemType
from helix.constants.anchor_type import AnchorType
from helix.constants import redis_constant
from test.test_helpers import *
class ArraySummaryDynamicTest(LiveServerTestCase):
def create_app(self):
redis_constant.redis_store = mockredis.mock_redis_client()
sql_constant.sql_session_maker = lambda: SQLManager.get_sql_session_maker('postgres://pivotal:@localhost/test')
app = main.app
app.config['TESTING'] = True
app.config['LIVESERVER_PORT'] = 8943
return app
@classmethod
def setUpClass(cls):
cls.test_db_session = test_db_session()
cls.browser = Browser('phantomjs')
@classmethod
def tearDownClass(cls):
cls.test_db_session.close_all()
cls.browser.quit()
def setUp(self):
self.browser.visit('/array_summary/')
def tearDown(self):
reset_db_session(self.test_db_session)
self.browser.cookies.delete()
def fill_in_site_characterization_data(self,
system_type=SystemType.singleTilt,
module_type=ModuleType.Cell96):
self.browser.visit(self.get_server_url() + '/site_characterization/')
self.browser.fill('project_name', 'Test Project Name')
self.browser.fill('building_width', '200')
self.browser.fill('building_height', '30')
self.browser.fill('building_length', '75.5')
self.browser.fill('wind_speed', '125')
self.browser.fill('ballast_block_weight', '13')
self.browser.fill('building_parapet_height', '0')
self.browser.fill('max_system_pressure', '10')
self.browser.select('system_type', system_type.value)
self.browser.select('anchor_type', AnchorType.EcoFasten.value)
self.browser.select('module_type', module_type.value)
self.browser.fill('design_spectral_response', '1.02')
self.browser.find_by_value('Next').first.click()
self.browser.visit(self.get_server_url() + '/array_summary/')
def upload_cad_block_file(self, file):
self.browser.attach_file('file_upload', file)
def upload_dxf_file(self, file='test/fixtures/dxf/input_single_tilt_96_cell.dxf'):
self.browser.attach_file('dxf_upload', file)
def test_shows_subarray_summary_table_if_user_has_uploaded_data(self):
self.fill_in_site_characterization_data()
self.upload_cad_block_file('test/fixtures/input_single_tilt_csv_for_bom.csv')
assert len(self.browser.find_by_css('.upload_field .error_message')) == 0
assert len(self.browser.find_by_css("#seismic_anchor_table")) > 0
eq_(len(self.browser.find_by_css("#seismic_anchor_table tr")), 4)
eq_(len(self.browser.find_by_css("#seismic_anchor_table tr:nth-child(2) td")), 13)
def test_shows_errors_if_user_tries_to_upload_select_invalid_data(self):
self.fill_in_site_characterization_data()
self.upload_cad_block_file('test/fixtures/invalid_wind_zone.csv')
assert len(self.browser.find_by_id('error_message_txt')) > 0
assert len(self.browser.find_by_css("#seismic_anchor_table")) == 0
# def test_shows_subarray_summary_table_after_user_uploads_dxf(self):
# self.fill_in_site_characterization_data(system_type=SystemType.dualTilt)
# self.upload_dxf_file('test/fixtures/dxf/input_dual_tilt_96_cell.dxf')
# assert len(self.browser.find_by_css('.upload_field .error_message')) == 0
# assert len(self.browser.find_by_css("#seismic_anchor_table")) > 0
# eq_(len(self.browser.find_by_css("#seismic_anchor_table tr")), 4)
# eq_(len(self.browser.find_by_css("#seismic_anchor_table tr:nth-child(2) td")), 8)
def test_indicates_that_user_has_already_uploaded_a_csv_file_after_uploading(self):
self.fill_in_site_characterization_data()
self.upload_cad_block_file('test/fixtures/input_single_tilt_csv_for_bom.csv')
assert len(self.browser.find_by_css('.upload_field .error_message')) == 0
assert 'input_single_tilt_csv_for_bom.csv' in self.browser.html
def test_adding_seismic_anchor_updates_subarray_summary(self):
self.fill_in_site_characterization_data()
self.upload_cad_block_file('test/fixtures/input_single_tilt_csv_for_bom.csv')
assert self.browser.is_element_visible_by_css('#arrayCanvas')
self.browser.execute_script('arrayVisualization.selectPanel(1631);')
self.browser.find_by_css('#add_seismic').click()
assert self.browser.find_by_css('#current_anchors td:nth-child(14)').text == '1'
self.browser.find_by_css('#add_seismic').click()
assert self.browser.find_by_css('#current_anchors td:nth-child(14)').text == '2'
def test_removig_seismic_anchor_updates_subarray_summary(self):
self.fill_in_site_characterization_data()
self.upload_cad_block_file('test/fixtures/input_single_tilt_csv_for_bom.csv')
assert self.browser.is_element_visible_by_css('#arrayCanvas')
self.browser.execute_script('arrayVisualization.selectPanel(1631);')
self.browser.find_by_css('#add_seismic').click()
assert self.browser.find_by_css('#current_anchors td:nth-child(14)').text == '1'
self.browser.find_by_css('#remove_seismic').click()
assert self.browser.find_by_css('#current_anchors td:nth-child(14)').text == '0'
# def test_indicate_that_user_has_uploaded_a_dxf_file_after_uploading_dxf(self):
# self.fill_in_site_characterization_data(system_type=SystemType.dualTilt)
# self.upload_dxf_file('test/fixtures/dxf/input_dual_tilt_96_cell.dxf')
# assert len(self.browser.find_by_css('.upload_field .error_message')) == 0
# assert 'input_dual_tilt_96_cell.dxf' in self.browser.html

View File

@@ -0,0 +1,417 @@
import json
import unittest
import flask
import mockredis
from numpy.testing import assert_array_equal
from splinter import Browser
from helix import main
from helix.constants import redis_constant
from helix.constants import sql_constant
from helix.constants.anchor_type import AnchorType
from helix.constants.dxf_validation import INVALID_DUAL_TILT_DESIGN
from helix.constants.file_validation_error import FileValidationMessage
from helix.constants.module_type import ModuleType
from helix.constants.seismic_anchor_validation_error import SeismicAnchorValidationError
from helix.constants.subarray import SUBARRAY_SIZE_BIG
from helix.constants.system_type import SystemType
from helix.session_manager import SessionManager
from test.integration.integration_test_helpers import assert_step_is_active, assert_step_is_completed
from test.test_helpers import *
class ArraySummaryTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.test_db_session = test_db_session()
@classmethod
def tearDownClass(cls):
cls.test_db_session.close_all()
def setUp(self):
redis_constant.redis_store = mockredis.mock_redis_client()
sql_constant.sql_session_maker = lambda: self.test_db_session
self.app = main.app.test_client()
self.browser = Browser('flask', app=main.app)
self.browser.visit('/array_summary/')
def tearDown(self):
reset_db_session(self.test_db_session)
def fill_in_site_characterization_data(self,
system_type=SystemType.singleTilt,
module_type=ModuleType.Cell96):
self.browser.visit('/site_characterization/')
self.browser.fill('project_name', 'Test Project Name')
self.browser.fill('building_width', '200')
self.browser.fill('building_height', '30')
self.browser.fill('building_length', '75.5')
self.browser.fill('wind_speed', '125')
self.browser.fill('exposure_category', 'C')
self.browser.fill('ballast_block_weight', '13')
self.browser.fill('building_parapet_height', '0')
self.browser.fill('max_system_pressure', '10')
self.browser.fill('system_type', system_type.value)
self.browser.fill('anchor_type', AnchorType.EcoFasten.value)
self.browser.fill('module_type', module_type.value)
self.browser.fill('design_spectral_response', '1.02')
self.browser.find_by_value('Next').first.click()
self.browser.visit('/array_summary/')
def upload_csv_file(self, file='test/fixtures/input_single_tilt.csv'):
self.browser.visit('/array_summary/')
self.browser.attach_file('file_upload', file)
self.browser.find_by_value('Next').first.click()
def upload_dxf_file(self, file='test/fixtures/dxf/input_dual_tilt_96_cell.dxf'):
self.browser.visit('/array_summary/')
self.browser.attach_file('dxf_upload', file)
self.browser.find_by_value('Next').first.click()
def assert_summary_values_column_data(self, column, label, value):
eq_(self.browser.find_by_css('.array_summary .summary_item:nth-child(%d) .item_title' % column).first.html,
label)
eq_(self.browser.find_by_css('.array_summary .summary_item:nth-child(%d) .item_body' % column).first.text,
value)
def test_does_not_show_tables_if_user_has_not_uploaded_site_data(self):
assert len(self.browser.find_by_css("#seismic_anchor_table")) == 0
assert len(self.browser.find_by_css("#summary_values_table")) == 0
def test_does_not_show_next_button(self):
self.fill_in_site_characterization_data()
next_button = self.browser.find_by_value('Next')
assert next_button.first._control.attrib['style'] == 'display: none;'
def test_without_csv_or_dxf_form_submission_requires_csv_attachment(self):
self.fill_in_site_characterization_data()
self.browser.find_by_value('Next').first.click()
assert self.browser.is_text_present("Please provide a .txt file!")
def test_with_invalid_csv_file(self):
self.fill_in_site_characterization_data(system_type=SystemType.dualTilt)
self.browser.attach_file('file_upload', 'test/fixtures/input_single_tilt.csv')
self.browser.find_by_value('Next').first.click()
eq_(self.browser.find_by_id('error_message_txt').first.value, 'Invalid wind zone for Dual Tilt System on line 1')
def test_shows_subarray_summary_table_if_user_has_uploaded_data(self):
self.fill_in_site_characterization_data()
self.upload_csv_file(file='test/fixtures/input_single_tilt_csv_for_bom.csv')
assert len(self.browser.find_by_css('.upload_field .error_message')) == 0
assert len(self.browser.find_by_css("#seismic_anchor_table")) > 0
eq_(len(self.browser.find_by_css("#seismic_anchor_table tr")), 4)
eq_(len(self.browser.find_by_css("#seismic_anchor_table tr:nth-child(2) td")), 13)
def test_indicate_that_user_has_already_uploaded_a_csv_file_after_uploading_csv(self):
self.fill_in_site_characterization_data()
self.upload_csv_file(file='test/fixtures/input_single_tilt_csv_for_bom.csv')
assert len(self.browser.find_by_css('.upload_field .error_message')) == 0
assert 'test/fixtures/input_single_tilt_csv_for_bom.csv' in self.browser.html
# def test_dxf_as_cad_file_if_dxf_file_uploaded(self):
# self.fill_in_site_characterization_data(system_type=SystemType.dualTilt)
# self.upload_dxf_file()
# assert len(self.browser.find_by_css('.upload_field .error_message')) == 0
# assert len(self.browser.find_by_css("#seismic_anchor_table")) > 0
# eq_(len(self.browser.find_by_css("#seismic_anchor_table tr")), 4)
# eq_(len(self.browser.find_by_css("#seismic_anchor_table tr:nth-child(2) td")), 8)
def test_indicate_that_user_has_uploaded_a_dxf_file_after_uploading_dxf(self):
self.fill_in_site_characterization_data(system_type=SystemType.dualTilt)
self.upload_dxf_file()
assert len(self.browser.find_by_css('.upload_field .error_message')) == 0
assert 'test/fixtures/dxf/input_dual_tilt_96_cell.dxf' in self.browser.html
def test_shows_errors_if_dxf_fails_validation(self):
self.fill_in_site_characterization_data(system_type=SystemType.dualTilt)
self.upload_dxf_file(file="test/fixtures/dxf/invalid_dual_tilt_96_cell_has_unpairable_modules.dxf")
assert len(self.browser.find_by_id('error_message_dxf')) == 1
msg = INVALID_DUAL_TILT_DESIGN
eq_(self.browser.find_by_id('error_message_dxf').first.value, msg)
def test_shows_errors_if_dxf_has_big_subarrays(self):
self.fill_in_site_characterization_data(system_type=SystemType.dualTilt)
self.upload_dxf_file(file="test/fixtures/dxf/invalid_dual_tilt_96_cell_subarrays_too_large.dxf")
assert len(self.browser.find_by_id('error_message_dxf')) == 1
msg = SUBARRAY_SIZE_BIG
eq_(self.browser.find_by_id('error_message_dxf').first.value, msg)
def test_viewing_summary_table_after_uploading_data_updates_navigation_header(self):
self.fill_in_site_characterization_data()
self.upload_csv_file(file='test/fixtures/input_single_tilt_csv_for_bom.csv')
assert_step_is_active(self.browser, 3)
assert_step_is_completed(self.browser, 3)
self.browser.click_link_by_partial_text("Back")
assert_step_is_active(self.browser, 3, False)
assert_step_is_completed(self.browser, 3)
def test_viewing_summary_table_before_uploading_data_does_not_update_navigation_header(self):
assert_step_is_active(self.browser, 3)
assert_step_is_completed(self.browser, 3, False)
self.browser.click_link_by_partial_text("Back")
assert_step_is_active(self.browser, 3, False)
assert_step_is_completed(self.browser, 3, False)
def test_next_is_not_available_before_uploading_data(self):
eq_(len(self.browser.find_by_value('Next')), 0)
self.fill_in_site_characterization_data()
self.upload_csv_file()
eq_(len(self.browser.find_by_value('Next')), 1)
def test_clicking_next_after_uploading_data_goes_to_power_station_configuration(self):
self.fill_in_site_characterization_data()
self.upload_csv_file()
self.browser.find_by_value('Next').first.click()
assert_step_is_active(self.browser, 4)
assert_step_is_active(self.browser, 3, False)
def test_shows_summary_values(self):
self.fill_in_site_characterization_data()
self.upload_csv_file(file='test/fixtures/input_single_tilt_csv_for_bom.csv')
self.assert_summary_values_column_data(1, 'Total System Weight (lbs)', '216,074')
self.assert_summary_values_column_data(2, 'Max PSF', '9.11')
self.assert_summary_values_column_data(3, 'Avg PSF', '5.05')
self.assert_summary_values_column_data(4, 'Total Anchors', '421')
self.assert_summary_values_column_data(5, 'Total Ballast', '10,150')
def test_shows_seismic_placement_interval(self):
self.fill_in_site_characterization_data(system_type=SystemType.dualTilt)
self.upload_csv_file(file='test/fixtures/input_small_dual_tilt.csv')
self.assert_summary_values_column_data(8, 'Seismic Anchor Max. Spacing', '14')
def test_clicking_back_goes_to_site_summary(self):
self.browser.click_link_by_partial_text('Back')
assert_step_is_active(self.browser, 2)
def test_getting_panel_data(self):
redis = redis_constant.redis_store
with self.app.session_transaction() as session:
session_manager = SessionManager(session, redis, self.test_db_session)
session_manager.save_form_submission({
'project_name': 'Test',
'building_width': 64,
'building_height': 24,
'building_length': 86,
'wind_speed': 110,
'exposure_category': 'B',
'ballast_block_weight': 14,
'building_parapet_height': 1,
'max_system_pressure': 10,
'system_type': SystemType.dualTilt.value,
'module_type': ModuleType.Cell96.value,
'anchor_type': AnchorType.EcoFasten.value,
'design_spectral_response': 1,
})
with open('test/fixtures/input_dual_subarray.txt', 'r', newline='') as csv_file:
session_manager.save_uploaded_file(csv_file.read())
response = self.app.get('/api/panel_data')
eq_(response.content_type, "application/json")
data = json.loads(response.data.decode())
expected = [
{'data': {'ballast': 7, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 1, 'panel_type': 2, 'psf': 4.94, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 2, 'panel_type': 2, 'psf': 3.12, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 11, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 3, 'panel_type': 1, 'psf': 6.47, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 11, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 4, 'panel_type': 1, 'psf': 6.47, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 6, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 5, 'panel_type': 1, 'psf': 4.57, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 6, 'panel_type': 2, 'psf': 3.12, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 7, 'panel_type': 4, 'psf': 2.26, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 8, 'panel_type': 3, 'psf': 2.98, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 9, 'panel_type': 3, 'psf': 2.98, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 0, 'cross_trays': 0, 'link_trays': 0, 'panel_id': 10, 'panel_type': 4, 'psf': 2.26, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 11, 'panel_type': 2, 'psf': 3.12, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 12, 'panel_type': 3, 'psf': 2.98, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 13, 'panel_type': 3, 'psf': 2.98, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 2, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 14, 'panel_type': 3, 'psf': 2.98, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 6, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 15, 'panel_type': 1, 'psf': 4.57, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 6, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 16, 'panel_type': 1, 'psf': 4.57, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 6, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 17, 'panel_type': 1, 'psf': 4.57, 'seismic_anchors': 0, 'subarray': 7, 'wind_anchors': 0, 'wind_zones': 'B'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 11, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 18, 'panel_type': 1, 'psf': 6.47, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 6, 'cross_trays': 0, 'link_trays': 1, 'panel_id': 19, 'panel_type': 3, 'psf': 4.48, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5},
{'data': {'ballast': 11, 'cross_trays': 0, 'link_trays': 2, 'panel_id': 20, 'panel_type': 1, 'psf': 6.47, 'seismic_anchors': 0, 'subarray': 8, 'wind_anchors': 0, 'wind_zones': 'A'}, 'height': 1, 'width': 1.5}
]
# Removing these keys as they get calculated differently in CircleCI
entries_to_remove = ('x', 'y')
for k in entries_to_remove:
for entry in data['panel_data']:
entry.pop(k, None)
assert_array_equal(data['panel_data'], expected)
def test_posting_user_seismic_anchors(self):
redis = redis_constant.redis_store
with self.app.session_transaction() as session:
session_manager = SessionManager(session, redis, self.test_db_session)
session_manager.save_form_submission({
'project_name': 'Test',
'building_width': 200,
'building_height': 30,
'building_length': 75.5,
'wind_speed': 125,
'exposure_category': 'C',
'ballast_block_weight': 13,
'building_parapet_height': 0,
'max_system_pressure': 0,
'system_type': SystemType.singleTilt.value,
'anchor_type': AnchorType.EcoFasten.value,
'design_spectral_response': 1.02,
})
with open('test/fixtures/input_single_tilt.csv', 'r', newline='') as csv_file:
session_manager.save_uploaded_file(csv_file.read())
result = self.app.post("/api/update_panel_data", content_type='application/json', data=json.dumps([
{"panel_id": 1, "seismic_anchors": 2},
{"panel_id": 2, "seismic_anchors": 0},
{"panel_id": 3, "seismic_anchors": 0},
{"panel_id": 4, "seismic_anchors": 0},
{"panel_id": 5, "seismic_anchors": 0},
{"panel_id": 6, "seismic_anchors": 0},
{"panel_id": 7, "seismic_anchors": 0},
{"panel_id": 8, "seismic_anchors": 0},
{"panel_id": 9, "seismic_anchors": 0},
{"panel_id": 10, "seismic_anchors": 0},
{"panel_id": 11, "seismic_anchors": 0},
{"panel_id": 12, "seismic_anchors": 0},
{"panel_id": 13, "seismic_anchors": 0},
{"panel_id": 14, "seismic_anchors": 0},
{"panel_id": 15, "seismic_anchors": 0},
{"panel_id": 16, "seismic_anchors": 0},
{"panel_id": 17, "seismic_anchors": 0},
{"panel_id": 18, "seismic_anchors": 0},
{"panel_id": 19, "seismic_anchors": 0},
]))
with self.app.session_transaction() as session:
session_manager = SessionManager(session, redis, self.test_db_session)
eq_(session_manager.user_values().user_override_seismic_anchors(), True)
expected_result = {
"status": "success",
"error": None,
"panel_data": [
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 2.08, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 1, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 2, 'panel_id': 1}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 2.04, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 3, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 2, 'seismic_anchors': 0, 'panel_id': 2}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 2.08, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 1, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 3}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 2.08, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 1, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 4}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 2.04, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 3, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 2, 'seismic_anchors': 0, 'panel_id': 5}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 2.04, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 3, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 2, 'seismic_anchors': 0, 'panel_id': 6}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 2.04, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 3, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 2, 'seismic_anchors': 0, 'panel_id': 7}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 2.08, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 1, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 8}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.89, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 2, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 9}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.89, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 2, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 10}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.89, 'wind_zones': 'I', 'ballast': 0, 'panel_type': 2, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 11}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.86, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 4, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 12}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.86, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 4, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 13}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.86, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 4, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 14}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.86, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 4, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 15}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.86, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 4, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 16}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.86, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 4, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 17}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.89, 'wind_zones': 'H', 'ballast': 0, 'panel_type': 2, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 18}},
{'x': 0, 'y': 0, 'height': 1, 'width': 1, 'data': {'subarray': 8, 'psf': 1.89, 'wind_zones': 'I', 'ballast': 0, 'panel_type': 2, 'cross_trays': 0, 'wind_anchors': 1, 'link_trays': 0, 'seismic_anchors': 0, 'panel_id': 19}},
],
"subarray_data": [
{"subarray": 8, "weight": 972, "required_seismic_anchors": 0},
],
}
received_result = flask.json.loads(result.data)
eq_(received_result, expected_result)
eq_(result.content_type, "application/json")
def test_posting_user_seismic_anchors_with_required_ones(self):
redis = redis_constant.redis_store
with self.app.session_transaction() as session:
session_manager = SessionManager(session, redis, self.test_db_session)
session_manager.save_form_submission({
'project_name': 'Test',
'building_width': 64,
'building_height': 24,
'building_length': 86,
'wind_speed': 110,
'exposure_category': 'B',
'ballast_block_weight': 14,
'building_parapet_height': 1,
'max_system_pressure': 10,
'system_type': SystemType.dualTilt.value,
'module_type': ModuleType.Cell96.value,
'anchor_type': AnchorType.EcoFasten.value,
'design_spectral_response': 1,
})
with open('test/fixtures/input_dual_subarray.txt', 'r', newline='') as csv_file:
session_manager.save_uploaded_file(csv_file.read())
result = self.app.post("/api/update_panel_data", content_type='application/json', data=json.dumps([
{"panel_id": 1, "seismic_anchors": 2},
{"panel_id": 2, "seismic_anchors": 0},
{"panel_id": 3, "seismic_anchors": 0},
{"panel_id": 4, "seismic_anchors": 2},
{"panel_id": 5, "seismic_anchors": 0},
{"panel_id": 6, "seismic_anchors": 0},
{"panel_id": 7, "seismic_anchors": 0},
{"panel_id": 8, "seismic_anchors": 0},
{"panel_id": 9, "seismic_anchors": 0},
{"panel_id": 10, "seismic_anchors": 0},
{"panel_id": 11, "seismic_anchors": 0},
{"panel_id": 12, "seismic_anchors": 0},
{"panel_id": 13, "seismic_anchors": 0},
{"panel_id": 14, "seismic_anchors": 0},
{"panel_id": 15, "seismic_anchors": 0},
{"panel_id": 16, "seismic_anchors": 0},
{"panel_id": 17, "seismic_anchors": 0},
{"panel_id": 18, "seismic_anchors": 0},
{"panel_id": 19, "seismic_anchors": 0},
{"panel_id": 20, "seismic_anchors": 0},
]))
with self.app.session_transaction() as session:
session_manager = SessionManager(session, redis, self.test_db_session)
eq_(session_manager.user_values().user_override_seismic_anchors(), True)
expected_result = [
{"subarray": 7, "weight": 2253, "required_seismic_anchors": 4},
{"subarray": 8, "weight": 673, "required_seismic_anchors": 0},
]
received_result = flask.json.loads(result.data)
eq_(received_result['subarray_data'], expected_result)
eq_(result.content_type, "application/json")
def test_posting_user_seismic_anchors_below_amount(self):
redis = redis_constant.redis_store
with self.app.session_transaction() as session:
session_manager = SessionManager(session, redis, self.test_db_session)
session_manager.save_form_submission({
'project_name': 'Test',
'building_width': 200,
'building_height': 300,
'building_length': 755,
'wind_speed': 200,
'exposure_category': 'B',
'ballast_block_weight': 20,
'building_parapet_height': 0,
'max_system_pressure': 100,
'system_type': SystemType.dualTilt.value,
'anchor_type': AnchorType.EcoFasten.value,
'design_spectral_response': 5.0,
})
with open('test/fixtures/input_dual_tilt.csv', 'r', newline='') as csv_file:
session_manager.save_uploaded_file(csv_file.read())
result = self.app.post("/api/update_panel_data", content_type='application/json', data=json.dumps([
{"panel_id": 1, "seismic_anchors": 0},
{"panel_id": 2, "seismic_anchors": 0},
{"panel_id": 3, "seismic_anchors": 0},
{"panel_id": 4, "seismic_anchors": 0},
{"panel_id": 5, "seismic_anchors": 0},
{"panel_id": 6, "seismic_anchors": 0},
{"panel_id": 7, "seismic_anchors": 0},
]))
with self.app.session_transaction() as session:
session_manager = SessionManager(session, redis, self.test_db_session)
eq_(session_manager.user_values().user_override_seismic_anchors(), False)
expected_result = {
"status": "error",
"error": SeismicAnchorValidationError.TooFewAnchors.value,
"panel_data": None,
"subarray_data": [
{"subarray": 1, "weight": 2739, "required_seismic_anchors": 6},
],
}
eq_(flask.json.loads(result.data), expected_result)
eq_(result.content_type, "application/json")

View File

@@ -0,0 +1,299 @@
import unittest
import mockredis
from numpy.testing import assert_array_equal
from helix.calculators.calculator import Calculator
from helix.constants.anchor_type import AnchorType
from helix.constants.exposure_category import ExposureCategory
from helix.constants.module_type import ModuleType
from helix.constants.system_type import SystemType
from helix.models.sql.sites import Site
from helix.store import Store
from helix.user_values import UserValues
class BomIntegrationTest(unittest.TestCase):
def test_lwoods_2381(self): # name of the site
self.store = mockredis.mock_redis_client()
self.site = Site()
self.values = UserValues(self.store, self.site)
self.site.building_height = 40
self.site.building_width = 87
self.site.building_length = 195
self.site.parapet_height = 0.3
self.site.wind_speed = 130
self.site.exposure_category = ExposureCategory.B.value
self.site.ballast_block_weight = 14
self.site.max_psf= 10
self.site.anchor_type = AnchorType.OMG_PowerGrip_Plus.value
self.site.spectral_response = 1
self.site.system_type = SystemType(SystemType.dualTilt.value)
self.site.module_type = ModuleType(ModuleType.Cell96.value)
with open('test/fixtures/input_dual_tilt_96_cell_lw_2381.tsv', 'r', newline='') as csv_file:
csv_content = csv_file.read()
self.site.cad_file = csv_content
self.subject = Calculator(self.values)
expected = [
["512200", "CLIP, WIRE FORMED, CABLE MANAGEMENT, INSIDE, 352MM ^ 2", "510"],
["513833", "TRAY, LINK, HELIX ROOF", "78"], # TODO: should be 74 for part perfect bom
["513841", "DEFLECTOR, LH, HELIX ROOF", "30"],
["513842", "DEFLECTOR, RH, HELIX ROOF", "30"],
["513843", "PLATE, ANCHOR, HELIX ROOF", "86"],
["513844", "TRAY, OPTIONAL BALLAST, HELIX ROOF", "35"],
["514056", "BASE, CHASSIS, DUAL TILT, HELIX ROOF", "147"],
["514057", "PLATFORM, CHASSIS, DUAL TILT, HELIX ROOF", "147"],
["514265", "FOOT, RECYCLED RUBBER, HELIX ROOF", "26"],
["515063", "SCREW, CAP, SH, M6 X 1 X 12, 18-8 SS (DIN 912)", "600"],
["515928", "FRONT SKIRT, HELIX ROOF", "126"],
["517871", "TRAY, LEADING, HELIX ROOF, RIVETED VERSION", "67"],
["518477", "WASHER, FLAT, 3/8, 1.00 OD, 18-8 SS", "100"],
["Contractor Supplied", "Ballast Blocks", "608"],
["TBD", "Anchors", "86"],
["TBD", "Modules", "252"]
]
bom = self.subject.compute_bom()
assert_array_equal(bom, expected)
def test_lw_2394(self):
self.store = Store(mockredis.mock_redis_client(), "foo")
self.site = Site()
self.values = UserValues(self.store, self.site)
self.site.building_height = 40
self.site.building_width = 88.25
self.site.building_length = 195.25
self.site.parapet_height = 0.3333
self.site.wind_speed = 130
self.site.exposure_category = ExposureCategory.B_C.value
self.site.exposure_transition_distance = 1212
self.site.ballast_block_weight = 14
self.site.max_psf= 10
self.site.anchor_type = AnchorType.OMG_PowerGrip_Plus.value
self.site.spectral_response = 0.993
self.site.system_type = SystemType(SystemType.dualTilt.value)
self.site.module_type = ModuleType(ModuleType.Cell96.value)
with open('test/fixtures/input_dual_tilt_96_cell_lw_2394.tsv', 'r', newline='') as csv_file:
csv_content = csv_file.read()
self.site.cad_file= csv_content
self.subject = Calculator(self.values)
expected = [
['512200', 'CLIP, WIRE FORMED, CABLE MANAGEMENT, INSIDE, 352MM ^ 2', '540'],
['513833', 'TRAY, LINK, HELIX ROOF', '75'], # TODO: should be 73 for part perfect bom
['513841', 'DEFLECTOR, LH, HELIX ROOF', '20'],
['513842', 'DEFLECTOR, RH, HELIX ROOF', '20'],
['513843', 'PLATE, ANCHOR, HELIX ROOF', '114'],
['513844', 'TRAY, OPTIONAL BALLAST, HELIX ROOF', '17'],
['514056', 'BASE, CHASSIS, DUAL TILT, HELIX ROOF', '148'],
['514057', 'PLATFORM, CHASSIS, DUAL TILT, HELIX ROOF', '148'],
['514265', 'FOOT, RECYCLED RUBBER, HELIX ROOF', '27'],
['515063', 'SCREW, CAP, SH, M6 X 1 X 12, 18-8 SS (DIN 912)', '600'],
['515928', 'FRONT SKIRT, HELIX ROOF', '130'],
['517871', 'TRAY, LEADING, HELIX ROOF, RIVETED VERSION', '69'],
['518477', 'WASHER, FLAT, 3/8, 1.00 OD, 18-8 SS', '125'],
['Contractor Supplied', 'Ballast Blocks', '358'],
['TBD', 'Anchors', '114'],
['TBD', 'Modules', '264']
]
bom = self.subject.compute_bom()
assert_array_equal(bom, expected)
def test_lw_5510(self):
self.store = Store(mockredis.mock_redis_client(), "foo")
self.site = Site()
self.values = UserValues(self.store, self.site)
self.site.building_height = 40
self.site.building_width = 86.75
self.site.building_length = 210.33
self.site.parapet_height = 1
self.site.wind_speed = 130
self.site.exposure_category = ExposureCategory.B.value
self.site.ballast_block_weight = 14
self.site.max_psf= 10
self.site.anchor_type = AnchorType.OMG_PowerGrip_Plus.value
self.site.spectral_response = 1.009
self.site.system_type = SystemType(SystemType.dualTilt.value)
self.site.module_type = ModuleType(ModuleType.Cell96.value)
with open('test/fixtures/input_dual_tilt_96_cell_lw_5510.tsv', 'r', newline='') as csv_file:
csv_content = csv_file.read()
self.site.cad_file = csv_content
self.subject = Calculator(self.values)
expected = [
['512200', 'CLIP, WIRE FORMED, CABLE MANAGEMENT, INSIDE, 352MM ^ 2', '330'],
['513833', 'TRAY, LINK, HELIX ROOF', '45'], # 45 is perfect value for this bom
['513841', 'DEFLECTOR, LH, HELIX ROOF', '36'],
['513842', 'DEFLECTOR, RH, HELIX ROOF', '36'],
['513843', 'PLATE, ANCHOR, HELIX ROOF', '67'],
['513844', 'TRAY, OPTIONAL BALLAST, HELIX ROOF', '13'],
['514056', 'BASE, CHASSIS, DUAL TILT, HELIX ROOF', '102'],
['514057', 'PLATFORM, CHASSIS, DUAL TILT, HELIX ROOF', '102'],
['514265', 'FOOT, RECYCLED RUBBER, HELIX ROOF', '16'],
['515063', 'SCREW, CAP, SH, M6 X 1 X 12, 18-8 SS (DIN 912)', '450'],
['515928', 'FRONT SKIRT, HELIX ROOF', '88'],
['517871', 'TRAY, LEADING, HELIX ROOF, RIVETED VERSION', '53'],
['518477', 'WASHER, FLAT, 3/8, 1.00 OD, 18-8 SS', '75'],
['Contractor Supplied', 'Ballast Blocks', '286'],
['TBD', 'Anchors', '67'],
['TBD', 'Modules', '160'],
]
bom = self.subject.compute_bom()
assert_array_equal(bom, expected)
def test_al_2400(self):
self.store = Store(mockredis.mock_redis_client(), "foo")
self.site = Site()
self.values = UserValues(self.store, self.site)
self.site.building_height = 40
self.site.building_width = 189
self.site.building_length = 81
self.site.parapet_height = 0
self.site.wind_speed = 130
self.site.exposure_category = ExposureCategory.B_C.value
self.site.exposure_transition_distance = 1500
self.site.module_type = ModuleType.Cell96.value
self.site.ballast_block_weight = 14
self.site.max_psf = 10
self.site.anchor_type = AnchorType.OMG_PowerGrip_Plus.value
self.site.spectral_response = 0.994
self.site.system_type = SystemType.dualTilt.value
with open('test/fixtures/input_dual_tilt_96_cell_al_2400.tsv', 'r', newline='') as csv_file:
csv_content = csv_file.read()
self.site.cad_file = csv_content
self.subject = Calculator(self.values)
expected = [
['512200', 'CLIP, WIRE FORMED, CABLE MANAGEMENT, INSIDE, 352MM ^ 2', '510'],
['513833', 'TRAY, LINK, HELIX ROOF', '73'], # TODO: should be 70 for part perfect bom
['513841', 'DEFLECTOR, LH, HELIX ROOF', '24'],
['513842', 'DEFLECTOR, RH, HELIX ROOF', '24'],
['513843', 'PLATE, ANCHOR, HELIX ROOF', '94'],
['513844', 'TRAY, OPTIONAL BALLAST, HELIX ROOF', '28'],
['514056', 'BASE, CHASSIS, DUAL TILT, HELIX ROOF', '140'],
['514057', 'PLATFORM, CHASSIS, DUAL TILT, HELIX ROOF', '140'],
['514265', 'FOOT, RECYCLED RUBBER, HELIX ROOF', '25'],
['515063', 'SCREW, CAP, SH, M6 X 1 X 12, 18-8 SS (DIN 912)', '600'],
['515928', 'FRONT SKIRT, HELIX ROOF', '120'],
['517871', 'TRAY, LEADING, HELIX ROOF, RIVETED VERSION', '64'],
['518477', 'WASHER, FLAT, 3/8, 1.00 OD, 18-8 SS', '100'],
['Contractor Supplied', 'Ballast Blocks', '560'],
['TBD', 'Anchors', '94'],
['TBD', 'Modules', '244'],
]
bom = self.subject.compute_bom()
assert_array_equal(bom, expected)
def test_array2(self):
"""
Pivotal: 1544689
"""
self.store = Store(mockredis.mock_redis_client(), "foo")
self.site = Site()
self.values = UserValues(self.store, self.site)
self.site.building_height = 45
self.site.building_width = 383
self.site.building_length = 430
self.site.parapet_height = 0.3
self.site.wind_speed = 110
self.site.exposure_category = ExposureCategory.C.value
self.site.exposure_transition_distance = 1500
self.site.module_type = ModuleType.Cell96.value
self.site.ballast_block_weight = 14
self.site.max_psf = 6
self.site.anchor_type = AnchorType.OMG_PowerGrip_Plus.value
self.site.spectral_response = 1.03
self.site.system_type = SystemType.dualTilt.value
with open('test/fixtures/input_dual_tilt_96_cell_array_2.tsv', 'r', newline='') as csv_file:
csv_content = csv_file.read()
self.site.cad_file = csv_content
self.subject = Calculator(self.values)
expected_link = [
['513833', 'TRAY, LINK, HELIX ROOF', '50'],
]
expected_link_num = expected_link[0][2]
bom = self.subject.compute_bom()
links_in_bom = [elem[2] for elem in bom if elem[0] == '513833'][0]
self.assertEqual(expected_link_num, links_in_bom)
def test_array1(self):
"""
Pivotal: 1544689
"""
self.store = Store(mockredis.mock_redis_client(), "foo")
self.site = Site()
self.values = UserValues(self.store, self.site)
self.site.building_height = 45
self.site.building_width = 383
self.site.building_length = 430
self.site.parapet_height = 0.3
self.site.wind_speed = 110
self.site.exposure_category = ExposureCategory.C.value
self.site.exposure_transition_distance = 1500
self.site.module_type = ModuleType.Cell96.value
self.site.ballast_block_weight = 14
self.site.max_psf = 10
self.site.anchor_type = AnchorType.OMG_PowerGrip_Plus.value
self.site.spectral_response = 1.03
self.site.system_type = SystemType.dualTilt.value
with open('test/fixtures/input_dual_tilt_96_cell_array_1.tsv', 'r', newline='') as csv_file:
csv_content = csv_file.read()
self.site.cad_file = csv_content
self.subject = Calculator(self.values)
expected_link = [
['513833', 'TRAY, LINK, HELIX ROOF', '37'],
]
expected_link_num = expected_link[0][2]
bom = self.subject.compute_bom()
links_in_bom = [elem[2] for elem in bom if elem[0] == '513833'][0]
self.assertEqual(expected_link_num, links_in_bom)
def test_array7(self):
"""
Pivotal: 1544689
"""
self.store = Store(mockredis.mock_redis_client(), "foo")
self.site = Site()
self.values = UserValues(self.store, self.site)
self.site.building_height = 45
self.site.building_width = 383
self.site.building_length = 430
self.site.parapet_height = 0.3
self.site.wind_speed = 110
self.site.exposure_category = ExposureCategory.C.value
self.site.exposure_transition_distance = 1500
self.site.module_type = ModuleType.Cell96.value
self.site.ballast_block_weight = 14
self.site.max_psf = 10
self.site.anchor_type = AnchorType.OMG_PowerGrip_Plus.value
self.site.spectral_response = 1.03
self.site.system_type = SystemType.dualTilt.value
with open('test/fixtures/input_dual_tilt_96_cell_array_7.tsv', 'r', newline='') as csv_file:
csv_content = csv_file.read()
self.site.cad_file = csv_content
self.subject = Calculator(self.values)
expected_link = [
['513833', 'TRAY, LINK, HELIX ROOF', '30'],
]
expected_link_num = expected_link[0][2]
bom = self.subject.compute_bom()
links_in_bom = [elem[2] for elem in bom if elem[0] == '513833'][0]
self.assertEqual(expected_link_num, links_in_bom)

View File

@@ -0,0 +1,265 @@
import os
import unittest
import mockredis
from splinter import Browser
from helix import main
from helix.constants import redis_constant
from helix.constants import sql_constant
from helix.constants.anchor_type import AnchorType
from helix.constants.inverter_type import InverterType
from helix.constants.system_type import SystemType
from helix.constants.version import version
from helix.models.sql.power_stations import PowerStation
from test.integration.integration_test_helpers import *
from test.test_helpers import test_db_session, reset_db_session
class FullUserFlowTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.test_db_session = test_db_session()
@classmethod
def tearDownClass(cls):
cls.test_db_session.close_all()
def setUp(self):
redis_constant.redis_store = mockredis.mock_redis_client()
sql_constant.sql_session_maker = lambda: self.test_db_session
self.app = main.app.test_client()
self.browser = Browser('flask', app=main.app)
self.browser.visit('/site_characterization/')
def tearDown(self):
reset_db_session(self.test_db_session)
def fill_in_site_characterization_data(self):
self.browser.fill('project_name', 'Test Project Name')
self.browser.fill('building_width', '200')
self.browser.fill('building_height', '30')
self.browser.fill('building_length', '75.5')
self.browser.fill('wind_speed', '125')
self.browser.fill('exposure_category', 'C')
self.browser.fill('ballast_block_weight', '13')
self.browser.fill('building_parapet_height', '0')
self.browser.fill('max_system_pressure', '10')
self.browser.fill('system_type', SystemType.singleTilt.value)
self.browser.fill('anchor_type', AnchorType.EcoFasten.value)
self.browser.fill('design_spectral_response', '1')
def attach_file(self, file):
self.browser.visit('/array_summary/')
self.browser.attach_file('file_upload', file)
def attach_dxf(self, dxf_file):
self.browser.visit('/array_summary/')
self.browser.attach_file('dxf_upload', dxf_file)
def advance_n_times(self, advance_count):
for _ in range(advance_count):
self.browser.find_by_value('Next').first.click()
def test_uploading_and_downloading_dual_tilt_csv(self):
"""
This is using data from an old-style file but works because
the old spacing is within the new spacing's variance tolerance
"""
with open('test/fixtures/expected_dual_tilt.csv', 'r', newline='') as csv_file:
csv_content = csv_file.read()
self.browser.fill('project_name', 'Test Project Name')
self.browser.fill('building_width', '1000')
self.browser.fill('building_height', '100')
self.browser.fill('building_length', '1500')
self.browser.fill('wind_speed', '110')
self.browser.fill('exposure_category', 'D')
self.browser.fill('ballast_block_weight', '20')
self.browser.fill('building_parapet_height', '0')
self.browser.fill('max_system_pressure', '17')
self.browser.fill('system_type', SystemType.dualTilt.value)
self.browser.fill('anchor_type', AnchorType.EcoFasten.value)
self.browser.fill('design_spectral_response', '1')
self.advance_n_times(1)
self.attach_file('test/fixtures/input_dual_tilt.csv')
self.advance_n_times(3)
self.browser.click_link_by_partial_text('Download AutoCAD import file')
eq_(self.browser.html, csv_content)
eq_(self.browser._response.headers['Content-Disposition'], 'attachment; filename=test_project_name_result.txt')
def test_clicking_back_at_download_goes_to_power_station_configuration(self):
self.browser.visit('/download/')
assert not self.browser.is_text_present('Next')
assert self.browser.is_text_present('Back')
self.browser.click_link_by_partial_text('Back')
assert_step_is_active(self.browser, 4)
def test_uploading_and_downloading_single_tilt_csv(self):
with open('test/fixtures/expected_single_tilt.csv', 'r', newline='') as csv_file:
csv_content = csv_file.read()
self.browser.fill('project_name', 'Test Project Name')
self.browser.fill('building_width', '1000')
self.browser.fill('building_height', '100')
self.browser.fill('building_length', '1500')
self.browser.fill('wind_speed', '110')
self.browser.fill('exposure_category', 'D')
self.browser.fill('ballast_block_weight', '20')
self.browser.fill('building_parapet_height', '0')
self.browser.fill('max_system_pressure', '17')
self.browser.fill('system_type', SystemType.singleTilt.value)
self.browser.fill('anchor_type', AnchorType.EcoFasten.value)
self.browser.fill('design_spectral_response', '1')
self.advance_n_times(1)
self.attach_file('test/fixtures/input_single_tilt.csv')
self.advance_n_times(3)
self.browser.click_link_by_partial_text('Download AutoCAD import file')
eq_(self.browser.html, csv_content)
eq_(self.browser._response.headers['Content-Disposition'], 'attachment; filename=test_project_name_result.txt')
def test_downloading_coordinates_csv(self):
with open('test/fixtures/expected_dual_tilt_coordinates.csv', 'r', newline='') as csv_file:
csv_content = csv_file.read()
self.browser.fill('project_name', 'Test Project Name')
self.browser.fill('building_width', '450')
self.browser.fill('building_height', '35')
self.browser.fill('building_length', '500')
self.browser.fill('wind_speed', '110')
self.browser.fill('exposure_category', 'B')
self.browser.fill('ballast_block_weight', '14')
self.browser.fill('building_parapet_height', '5')
self.browser.fill('max_system_pressure', '10')
self.browser.fill('system_type', SystemType.dualTilt.value)
self.browser.fill('anchor_type', AnchorType.OMG_PowerGrip_Plus.value)
self.browser.fill('design_spectral_response', '1.5')
self.advance_n_times(1)
self.attach_file('test/fixtures/input_dual_tilt_coordinates.csv')
self.advance_n_times(1)
self.browser.visit('/download/')
self.browser.click_link_by_partial_text('Download AutoCAD import file')
eq_(self.browser.html, csv_content)
eq_(self.browser._response.headers['Content-Disposition'], 'attachment; filename=test_project_name_result.txt')
# def test_uploading_dxf_and_downloading_csv(self):
# self.browser.fill('project_name', 'Test Project Name')
# self.browser.fill('building_width', '100')
# self.browser.fill('building_height', '30')
# self.browser.fill('building_length', '100')
# self.browser.fill('building_parapet_height', '5')
# self.browser.fill('wind_speed', '130')
# self.browser.fill('exposure_category', 'C')
# self.browser.fill('ballast_block_weight', '14')
# self.browser.fill('max_system_pressure', '12')
# self.browser.fill('system_type', SystemType.dualTilt.value)
# self.browser.fill('anchor_type', AnchorType.OMG_PowerGrip_Plus.value)
# self.browser.fill('design_spectral_response', '0.5')
#
# self.advance_n_times(1)
# self.attach_dxf('test/fixtures/dxf/input_dual_tilt_96_cell.dxf')
#
# self.advance_n_times(1)
# self.browser.visit('/download/')
# self.browser.click_link_by_partial_text('Download AutoCAD import file')
# assert self.browser.html != ''
# eq_(self.browser._response.headers['Content-Disposition'], 'attachment; filename=test_project_name_result.txt')
def test_downloading_documentation(self):
self.fill_in_site_characterization_data()
self.advance_n_times(1)
self.attach_file('test/fixtures/input_single_tilt_csv_for_bom.csv')
self.advance_n_times(3)
os.environ['SP_DOCGEN_API_KEY'] = 'DC97-20AF-567E'
self.browser.click_link_by_partial_text('Download Project Report')
os.environ['SP_DOCGEN_API_KEY'] = ''
# with open('test/fixtures/expected_single_tilt_documentation.pdf', 'r', newline='') as pdf_file:
# expected_doc = pdf_file.read()
#
# eq_(self.browser.html, expected_doc)
eq_(self.browser._response.headers['Content-Disposition'], 'attachment; filename=test_project_name_documentation.pdf')
def test_downloading_BOM_dual_tilt(self):
assert len(self.browser.find_by_text('Download BOM')) == 0
self.fill_in_site_characterization_data()
self.browser.fill('system_type', SystemType.dualTilt.value)
self.advance_n_times(1)
self.attach_file('test/fixtures/input_dual_tilt_csv_for_bom.csv')
self.advance_n_times(3)
self.browser.click_link_by_partial_text('Download BOM')
with open('test/fixtures/expected_dual_tilt_bom.csv', 'r', newline='') as csv_file:
expected_bom = csv_file.read()
eq_(self.browser.html, expected_bom)
eq_(self.browser._response.headers['Content-Disposition'], 'attachment; filename=test_project_name_bom.txt')
def test_downloading_BOM_single_tilt(self):
assert len(self.browser.find_by_text('Download BOM')) == 0
self.fill_in_site_characterization_data()
self.advance_n_times(1)
self.attach_file('test/fixtures/input_single_tilt_csv_for_bom.csv')
self.advance_n_times(2)
fill_in_power_station(self.browser, name='Test Power Station Name', count=1, inverter_count=3,
inverters=[
{
'model': InverterType.SMA.MODEL_15KW,
'strings': 3,
'sunshade': True,
'dc_switch': True
},
{
'model': InverterType.SMA.MODEL_12KW,
'sunshade': True,
'dc_switch': True,
'strings': 4
},
{
'model': InverterType.SMA.MODEL_24KW,
'sunshade': True,
'dc_switch': True,
'strings': 7
}
])
submit_power_station_form(self.browser)
first_power_station_id = str(self.test_db_session.query(PowerStation).first().id)
fill_in_power_station(self.browser, count=5, inverter_count=2,
inverters=[
{
'model': InverterType.SMA.MODEL_24KW,
'strings': 8,
},
{
'model': InverterType.SMA.MODEL_12KW,
'strings': 2
}
])
submit_power_station_form(self.browser)
fill_in_standalone_inverter(self.browser, model=InverterType.SMA.MODEL_15KW, sunshade=True, dc_switch=True, strings=3)
submit_standalone_inverter_form(self.browser)
fill_in_standalone_inverter(self.browser, model=InverterType.SMA.MODEL_15KW, sunshade=True, dc_switch=True, strings=3)
self.browser.fill('attachment_point', first_power_station_id)
submit_standalone_inverter_form(self.browser)
self.advance_n_times(1)
self.browser.click_link_by_partial_text('Download BOM')
with open('test/fixtures/expected_single_tilt_bom.csv', 'r', newline='') as csv_file:
expected_bom = csv_file.read()
eq_(self.browser.html, expected_bom)
eq_(self.browser._response.headers['Content-Disposition'], 'attachment; filename=test_project_name_bom.txt')

View File

@@ -0,0 +1,170 @@
from nose.tools import eq_
from helix.constants.inverter_brand import InverterBrand
from helix.constants.inverter_type import InverterType
def assert_step_is_active(browser, step_number, is_active=True):
eq_(browser.find_by_css('.navigation_header #step_%d' % step_number).has_class('active'), is_active)
def assert_step_is_completed(browser, step_number, is_completed=True):
eq_(browser.find_by_css('.navigation_header #step_%d' % step_number).has_class('completed'), is_completed)
def select_inverter_brand(browser, brand=None):
if brand not in InverterBrand.all():
raise ValueError('Brand must be a valid InverterBrand')
browser.select('inverter_brand_id', str(brand))
def submit_inverter_brand_form(browser):
browser.find_by_css('#inverter_brand_form input[type=submit]').first.click()
def fill_in_power_station(browser, name=None, count=None, run_length=None, inverter_count=None, inverters=()):
if name is not None:
browser.fill('power_station_description', str(name))
if count is not None:
browser.fill('power_station_quantity', str(count))
if run_length is not None:
browser.fill('ac_run_length', str(run_length))
if inverter_count:
browser.select('inverter_quantity', str(inverter_count))
for index, inverter in enumerate(inverters):
prefix = 'inverter_%d-' % (index + 1)
if inverter.get('model'):
browser.select(prefix + 'model', str(inverter['model'].value))
if inverter.get('strings'):
browser.select(prefix + 'strings_per_inverter', str(inverter['strings']))
if inverter.get('sunshade'):
browser.check(prefix + 'sunshade')
if inverter.get('dc_switch'):
browser.check(prefix + 'dc_switch')
def submit_power_station_form(browser):
browser.find_by_value('Add').first.click()
def show_power_station_form(browser):
browser.find_by_css('#add_new_power_station').first.click()
def assert_power_station_table_row(browser, row_number, name=None, models=None, strings=None, quantity=None):
row = browser.find_by_css('.power_station_table tr:nth-child(%d)' % (row_number + 1)).first
if name:
eq_(row.find_by_css('td:nth-child(1)').first.text.strip(), name)
if models and strings:
eq_(len(row.find_by_css('td:nth-child(2)').find_by_tag('p')), len(models))
eq_(len(row.find_by_css('td:nth-child(3)').find_by_tag('p')), len(strings))
for i in range(len(models)):
eq_(row.find_by_css('td:nth-child(2)').find_by_tag('p')[i].text, models[i])
eq_(row.find_by_css('td:nth-child(3)').find_by_tag('p')[i].text, strings[i])
if quantity:
eq_(row.find_by_css('td:nth-child(4)').first.text, str(quantity))
def edit_power_station(browser, row_number):
browser.find_by_css('.power_station_table tr:nth-child(%d) i.icon-pencil' % (row_number + 1)).first.click()
def assert_number_of_power_station_table_rows(browser, number_of_rows):
eq_(len(browser.find_by_css('.power_station_table tr')), number_of_rows + 1)
# Standalone Inverters
def show_standalone_inverter_form(browser):
browser.find_by_css('#add_standalone_inverter').first.click()
def fill_in_standalone_inverter(browser, quantity=None, model=None, strings=None,
sunshade=None, dc_switch=None, ac_run_length=None, splice_box=None):
if quantity:
browser.select('inverter-quantity', str(quantity))
if model:
browser.select('inverter-model', str(model.value))
if strings:
browser.select('inverter-strings_per_inverter', str(strings))
if sunshade:
browser.check('inverter-sunshade')
if dc_switch:
browser.check('inverter-dc_switch')
if ac_run_length:
browser.fill('standalone_ac_run_length', str(ac_run_length))
if splice_box:
browser.check('inverter-splice_box')
def submit_standalone_inverter_form(browser):
browser.find_by_css('#standalone_inverter_form input[type=submit]').first.click()
def edit_standalone_inverter(browser, row_number):
browser.find_by_css('.standalone_inverter_table tr:nth-child(%d) i.icon-pencil' % (row_number + 1)).first.click()
def assert_number_of_standalone_inverter_table_rows(browser, number_of_rows):
eq_(len(browser.find_by_css('.standalone_inverter_table tr')), number_of_rows + 1)
def assert_standalone_inverter_row(browser, row_number, model=None, interconnect=None, strings=None, sunshade=None,
dc_switch=None, splice_box=None):
first_table_row = browser.find_by_css('.standalone_inverter_table tr:nth-child(%d)' % (row_number + 1)).first
if model is not None:
eq_(first_table_row.find_by_css('td:nth-child(1)').first.text, model.label)
if interconnect is not None:
eq_(first_table_row.find_by_css('td:nth-child(2)').first.text, interconnect)
if strings is not None:
eq_(first_table_row.find_by_css('td:nth-child(3)').first.text, str(strings))
if sunshade is not None:
eq_(first_table_row.find_by_css('td:nth-child(5) i').has_class('hidden'), not sunshade)
if dc_switch is not None:
eq_(first_table_row.find_by_css('td:nth-child(4) i').has_class('hidden'), not dc_switch)
if splice_box is not None:
eq_(first_table_row.find_by_css('td:nth-child(3) i').has_class('hidden'), not splice_box)
def assert_standalone_inverter_string_options(browser, expected_list):
inverter_strings_field_options = browser.find_by_css('#inverter-strings_per_inverter option')
inverter_strings_field_options_list = list(map(lambda x: x.html, inverter_strings_field_options))
eq_(len(inverter_strings_field_options_list), len(expected_list))
eq_(inverter_strings_field_options_list, expected_list)
def show_supervisor_monitor_form(browser):
browser.find_by_css("#add_supervisor_monitor").first.click()
# PV Supervisor Monitoring
def show_monitoring_form(browser):
browser.find_by_css('#add_supervisor_monitor').first.click()
def fill_in_monitoring_form(browser, power_source):
browser.fill('power_source', power_source)
def submit_monitoring_form(browser):
browser.find_by_css('#supervisor_monitor_form input[type=submit]').first.click()
def edit_supervisor_monitor(browser, row_number):
browser.find_by_css('.supervisor_monitor_table tr:nth-child(%d) i.icon-pencil' % (row_number + 1)).first.click()
def assert_number_of_supervisor_monitor_table_rows(browser, number_of_rows):
eq_(len(browser.find_by_css('.supervisor_monitor_table tr')), number_of_rows + 1)
def assert_supervisor_monitor_row(browser, row_number, power_source):
table_row = browser.find_by_css('.supervisor_monitor_table tr:nth-child(%d)' % (row_number + 1)).first
eq_(table_row.find_by_css('td:nth-child(1)').first.text, power_source)

View File

@@ -0,0 +1,503 @@
from flask.ext.testing import LiveServerTestCase
import mockredis
from splinter import Browser
from helix import main
from helix.constants import redis_constant
from helix.constants import sql_constant
from helix.constants.inverter_type import InverterType
from helix.constants.system_type import SystemType
from helix.db.sql_manager import SQLManager
from helix.models.sql.power_monitors import PowerMonitor
from helix.models.sql.power_stations import PowerStation
from helix.models.sql.standalone_inverters import StandaloneInverter
from test.integration.integration_test_helpers import *
from test.test_helpers import test_db_session, reset_db_session
class PowerStationConfigurationDynamicFormTestCase(LiveServerTestCase):
def create_app(self):
redis_constant.redis_store = mockredis.mock_redis_client()
sql_constant.sql_session_maker = lambda: SQLManager.get_sql_session_maker('postgres://pivotal:@localhost/test')
app = main.app
app.config['TESTING'] = True
app.config['LIVESERVER_PORT'] = 8943
return app
@classmethod
def setUpClass(cls):
cls.test_db_session = test_db_session()
cls.browser = Browser('phantomjs')
@classmethod
def tearDownClass(cls):
cls.test_db_session.close_all()
cls.browser.quit()
def setUp(self):
self.fill_in_site_characterization_data()
self.browser.visit(self.get_server_url() + '/power_station_configuration/')
def tearDown(self):
reset_db_session(self.test_db_session)
self.browser.cookies.delete()
def fill_in_site_characterization_data(self, system_type=SystemType.singleTilt):
self.browser.visit(self.get_server_url() + '/site_characterization/')
self.browser.fill('project_name', 'Test Project Name')
self.browser.fill('building_width', '200')
self.browser.fill('building_height', '30')
self.browser.fill('building_length', '75.5')
self.browser.fill('wind_speed', '125')
self.browser.fill('ballast_block_weight', '13')
self.browser.fill('building_parapet_height', '0')
self.browser.fill('max_system_pressure', '10')
self.browser.fill('design_spectral_response', '1.02')
self.browser.select('system_type', system_type.value)
self.browser.find_by_value('Next').first.click()
def expect_power_station_input_field(self, name='Test Power Station Name', uuid=None, count=2, run_length=None,
inverter_count=3):
eq_(self.browser.find_by_name('power_station_description').first.value, name)
if uuid:
eq_(self.browser.find_by_name('power_station_id').first.value, uuid)
eq_(self.browser.find_by_name('power_station_quantity').first.value, str(count))
if run_length:
eq_(self.browser.find_by_name('ac_run_length').first.value, str(run_length))
eq_(self.browser.find_by_name('inverter_quantity').first.value, str(inverter_count))
eq_(self.browser.find_by_name('inverter_1-model').first.value, str(InverterType.SMA.MODEL_15KW.value))
eq_(self.browser.find_by_name('inverter_1-strings_per_inverter').first.value, '3')
eq_(self.browser.find_by_name('inverter_1-sunshade').first.checked, False)
eq_(self.browser.find_by_name('inverter_1-dc_switch').first.checked, False)
def test_switching_inverter_brand_removes_power_station_section(self):
select_inverter_brand(self.browser, brand=InverterBrand.DELTA.value)
assert self.browser.is_element_not_present_by_css('#add_new_power_station')
assert self.browser.is_element_not_present_by_css('form[name=power_station_form]')
def test_switching_inverter_brand_shows_different_inverter_models(self):
show_standalone_inverter_form(self.browser)
eq_(len(self.browser.find_by_css('#inverter-model option')), 4)
eq_(self.browser.find_by_name('inverter-model').first.value, str(InverterType.SMA.default_value()))
select_inverter_brand(self.browser, brand=InverterBrand.DELTA.value)
show_standalone_inverter_form(self.browser)
eq_(len(self.browser.find_by_css('#inverter-model option')), 3)
eq_(self.browser.find_by_name('inverter-model').first.value, str(InverterType.DELTA.default_value()))
def test_switching_inverter_brand_hides_attachment_point(self):
show_power_station_form(self.browser)
fill_in_power_station(self.browser, name='Test Power Station Name', count=1, inverter_count=3)
submit_power_station_form(self.browser)
attachment_point_options = self.browser.find_by_css('#attachment_point option')
eq_(len(attachment_point_options), 2)
attachment_options_as_list = list(map(lambda x: x.html, attachment_point_options))
eq_(attachment_options_as_list, ['Switch Gear', 'Test Power Station Name'])
select_inverter_brand(self.browser, brand=InverterBrand.DELTA.value)
show_standalone_inverter_form(self.browser)
assert self.browser.is_element_not_present_by_css('#attachment_point')
def test_inverters_without_string_range_hides_string_select(self):
show_standalone_inverter_form(self.browser)
assert self.browser.is_element_visible_by_css('#inverter-strings_per_inverter_row')
select_inverter_brand(self.browser, brand=InverterBrand.DELTA.value)
show_standalone_inverter_form(self.browser)
assert not self.browser.is_element_visible_by_css('#inverter-strings_per_inverter_row')
def test_inverter_input_is_based_on_number_selected(self):
show_power_station_form(self.browser)
assert self.browser.is_text_present('Inverter 1')
assert self.browser.is_text_present('Inverter 2')
assert self.browser.is_text_present('Inverter 3')
assert self.browser.is_text_present('Inverter 4')
self.browser.select('inverter_quantity', '2')
assert self.browser.is_text_present('Inverter 1')
assert self.browser.is_text_present('Inverter 2')
assert not self.browser.is_text_present('Inverter 3')
assert not self.browser.is_text_present('Inverter 4')
self.browser.select('inverter_quantity', '3')
assert self.browser.is_text_present('Inverter 1')
assert self.browser.is_text_present('Inverter 2')
assert self.browser.is_text_present('Inverter 3')
assert not self.browser.is_text_present('Inverter 4')
def test_data_deleted_when_switching_brand(self):
show_power_station_form(self.browser)
fill_in_power_station(self.browser, name='Test Power Station Name', count=1, inverter_count=3)
submit_power_station_form(self.browser)
show_standalone_inverter_form(self.browser)
fill_in_standalone_inverter(self.browser, model=InverterType.SMA.MODEL_20KW)
submit_standalone_inverter_form(self.browser)
show_standalone_inverter_form(self.browser)
fill_in_standalone_inverter(self.browser, model=InverterType.SMA.MODEL_15KW)
submit_standalone_inverter_form(self.browser)
show_monitoring_form(self.browser)
submit_monitoring_form(self.browser)
select_inverter_brand(self.browser, brand=InverterBrand.DELTA.value)
assert_number_of_power_station_table_rows(self.browser, -1)
assert_number_of_standalone_inverter_table_rows(self.browser, -1)
assert_number_of_supervisor_monitor_table_rows(self.browser, -1)
def test_inverter_strings_options_same_for_all_inverter_capacity_options_single_tilt(self):
show_power_station_form(self.browser)
inverter_strings_field = self.browser.find_by_name('inverter_1-strings_per_inverter').first
inverter_strings_options = inverter_strings_field.find_by_css('option:enabled')
eq_(list(map(lambda x: x.value, inverter_strings_options)), ['2', '3', '4', '5', '6', '7', '8'])
eq_(inverter_strings_field.value, '8')
string_counts = [4, 5, 6, 8]
for idx, inverter_type in enumerate(InverterType.SMA.all()):
self.browser.select('inverter_1-model', inverter_type.value)
inverter_strings_field = self.browser.find_by_name('inverter_1-strings_per_inverter').first
eq_(inverter_strings_field.value, str(string_counts[idx]))
inverter_strings_options = inverter_strings_field.find_by_css('option:enabled')
eq_(list(map(lambda x: x.value, inverter_strings_options)), ['2', '3', '4', '5', '6', '7', '8'])
def test_inverter_strings_options_same_for_all_inverter_capacity_options_dual_tilt(self):
self.fill_in_site_characterization_data(system_type=SystemType.dualTilt)
self.browser.visit(self.get_server_url() + '/power_station_configuration/')
show_power_station_form(self.browser)
inverter_strings_field = self.browser.find_by_name('inverter_1-strings_per_inverter').first
inverter_strings_options = inverter_strings_field.find_by_css('option:enabled')
eq_(list(map(lambda x: x.value, inverter_strings_options)), ['2', '4', '6', '8'])
eq_(inverter_strings_field.value, '8')
string_counts = [4, 4, 6, 8]
for idx, inverter_type in enumerate(InverterType.SMA.all()):
self.browser.select('inverter_1-model', inverter_type.value)
inverter_strings_field = self.browser.find_by_name('inverter_1-strings_per_inverter').first
eq_(inverter_strings_field.value, str(string_counts[idx]))
inverter_strings_options = inverter_strings_field.find_by_css('option:enabled')
eq_(list(map(lambda x: x.value, inverter_strings_options)), ['2', '4', '6', '8'])
def test_add_power_station_button_shows_power_station_input_form(self):
show_power_station_form(self.browser)
fill_in_power_station(self.browser, name='Test Power Station Name', count=2)
submit_power_station_form(self.browser)
assert not self.browser.find_by_css('#power_station_form').first.visible
assert not self.browser.find_by_css('#add_new_power_station').first.has_class('disabled')
show_power_station_form(self.browser)
assert self.browser.find_by_css('#power_station_form').first.visible
assert self.browser.find_by_css('#add_new_power_station').first.has_class('disabled')
def test_editing_power_stations(self):
show_power_station_form(self.browser)
fill_in_power_station(self.browser,
name='Test Power Station Name',
count=2,
run_length=102,
inverter_count=3,
inverters=[
{
'model': InverterType.SMA.MODEL_15KW,
'strings': 3,
}
])
submit_power_station_form(self.browser)
saved_id = str(self.test_db_session.query(PowerStation).first().id)
show_power_station_form(self.browser)
fill_in_power_station(self.browser,
count=3,
inverter_count=2,
inverters=[{'model': InverterType.SMA.MODEL_20KW}])
submit_power_station_form(self.browser)
edit_power_station(self.browser, 1)
assert self.browser.find_by_css('#power_station_form').first.visible
eq_(self.browser.find_by_name('power_station_description').first.value, 'Test Power Station Name')
eq_(self.browser.find_by_name('power_station_id').first.value, saved_id)
eq_(self.browser.find_by_name('power_station_quantity').first.value, '2')
eq_(self.browser.find_by_name('ac_run_length').first.value, '102')
eq_(self.browser.find_by_name('inverter_quantity').first.value, '3')
assert self.browser.is_text_present('Inverter 3')
eq_(self.browser.find_by_name('inverter_1-model').first.value, str(InverterType.SMA.MODEL_15KW.value))
eq_(self.browser.find_by_name('inverter_1-strings_per_inverter').first.value, '3')
eq_(self.browser.find_by_name('inverter_1-sunshade').first.checked, False)
eq_(self.browser.find_by_name('inverter_1-dc_switch').first.checked, False)
fill_in_power_station(self.browser, name='Extra Special Power Station', inverter_count=4,
inverters=[{'model': InverterType.SMA.MODEL_12KW}])
self.browser.find_by_value('Save').first.click()
assert_number_of_power_station_table_rows(self.browser, 2)
models = ['12kW SMA Tripower - 514686', '24kW SMA Tripower - 514685', '24kW SMA Tripower - 514685', '24kW SMA Tripower - 514685']
strings = ['4', '8', '8', '8']
assert_power_station_table_row(self.browser, 1,
name='Extra Special Power Station',
models=models,
strings=strings,
quantity=2)
def test_editing_power_station_styles_current_row_as_selected(self):
show_power_station_form(self.browser)
submit_power_station_form(self.browser)
show_power_station_form(self.browser)
submit_power_station_form(self.browser)
edit_power_station(self.browser, 1)
assert self.browser.find_by_css('.power_station_table tr:nth-child(2)').has_class('selected')
assert not self.browser.find_by_css('.power_station_table tr:nth-child(3)').has_class('selected')
edit_power_station(self.browser, 2)
assert not self.browser.find_by_css('.power_station_table tr:nth-child(2)').has_class('selected')
assert self.browser.find_by_css('.power_station_table tr:nth-child(3)').has_class('selected')
def test_cancelling_editing_power_stations(self):
show_power_station_form(self.browser)
fill_in_power_station(self.browser, name='Test Power Station Name')
submit_power_station_form(self.browser)
saved_id = str(self.test_db_session.query(PowerStation).first().id)
edit_power_station(self.browser, 1)
fill_in_power_station(self.browser, name='Extra Special Power Station')
self.browser.click_link_by_text('Cancel')
assert not self.browser.find_by_css('#power_station_form').first.visible
assert not self.browser.find_by_name('power_station_id').first.value == saved_id
eq_(self.browser.find_by_name('power_station_description').first.value, 'Power Station 2')
assert_number_of_power_station_table_rows(self.browser, 1)
assert_power_station_table_row(self.browser, 1, name='Test Power Station Name')
def test_deleting_power_station_from_edit_position(self):
show_power_station_form(self.browser)
assert not self.browser.is_text_present('Delete')
submit_power_station_form(self.browser)
errors = self.browser.find_by_css('.error_message')
assert len(errors) == 0, "expected no errors, got %s errors" % str([error.value for error in errors])
assert_number_of_power_station_table_rows(self.browser, 1)
edit_power_station(self.browser, 1)
assert self.browser.is_text_present('Delete')
self.browser.click_link_by_text('Delete')
eq_(len(self.browser.find_by_css('.power_station_table')), 0)
def test_able_to_toggle_between_adding_standalone_inverter_and_power_station_and_monitoring(self):
assert not self.browser.find_by_css('#add_standalone_inverter').first.has_class('disabled')
show_standalone_inverter_form(self.browser)
assert self.browser.find_by_css('#add_standalone_inverter').first.has_class('disabled')
assert not self.browser.find_by_css('#add_new_power_station').first.has_class('disabled')
assert not self.browser.find_by_css('#add_supervisor_monitor').first.has_class('disabled')
assert not self.browser.is_text_present('Power Station Description')
assert not self.browser.is_text_present('Power Source')
assert self.browser.is_text_present('Attachment Point')
show_power_station_form(self.browser)
assert not self.browser.find_by_css('#add_standalone_inverter').first.has_class('disabled')
assert not self.browser.find_by_css('#add_supervisor_monitor').first.has_class('disabled')
assert self.browser.find_by_css('#add_new_power_station').first.has_class('disabled')
assert self.browser.is_text_present('Power Station Description')
assert not self.browser.is_text_present('Power Source')
assert not self.browser.is_text_present('Attachment Point')
show_monitoring_form(self.browser)
assert self.browser.find_by_css('#add_supervisor_monitor').first.has_class('disabled')
assert not self.browser.find_by_css('#add_standalone_inverter').first.has_class('disabled')
assert not self.browser.find_by_css('#add_standalone_inverter').first.has_class('disabled')
assert not self.browser.is_text_present('Power Station Description')
assert not self.browser.is_text_present('Attachment Point')
assert self.browser.is_text_present('Power Source')
def test_adding_stand_alone_inverter(self):
show_standalone_inverter_form(self.browser)
fill_in_standalone_inverter(self.browser, model=InverterType.SMA.MODEL_20KW)
eq_(self.browser.find_by_name('inverter-strings_per_inverter').first.value, '6')
attachment_point_field = self.browser.find_by_name('attachment_point').first
attachment_point_options = attachment_point_field.find_by_css('option')
eq_(len(attachment_point_options), 1)
def test_adding_stand_alone_inverter_shows_quantity(self):
show_standalone_inverter_form(self.browser)
assert self.browser.is_element_visible_by_css('#inverter-quantity')
def test_editing_stand_alone_inverter_hides_quantity(self):
show_standalone_inverter_form(self.browser)
fill_in_standalone_inverter(self.browser)
submit_standalone_inverter_form(self.browser)
edit_standalone_inverter(self.browser, 1)
assert self.browser.is_element_not_visible_by_css('#inverter-quantity')
def test_adding_stand_alone_inverter_allows_previously_added_power_stations_as_attachment_points(self):
show_power_station_form(self.browser)
fill_in_power_station(self.browser, name='Test Power Station Name', count=1, inverter_count=4)
submit_power_station_form(self.browser)
show_power_station_form(self.browser)
fill_in_power_station(self.browser, name='Test Power Station Name 2', count=1, inverter_count=3)
submit_power_station_form(self.browser)
show_standalone_inverter_form(self.browser)
attachment_point_field = self.browser.find_by_name('attachment_point').first
attachment_point_options = attachment_point_field.find_by_css('option')
eq_(len(attachment_point_options), 2)
eq_(attachment_point_options.first.text, 'Switch Gear')
eq_(attachment_point_options.last.text, 'Test Power Station Name 2')
def test_editing_sma_standalone_inverters(self):
show_standalone_inverter_form(self.browser)
fill_in_standalone_inverter(self.browser, model=InverterType.SMA.MODEL_12KW, strings=3, dc_switch=True, ac_run_length=453)
submit_standalone_inverter_form(self.browser)
assert_number_of_standalone_inverter_table_rows(self.browser, 1)
saved_id = str(self.test_db_session.query(StandaloneInverter).first().id)
show_standalone_inverter_form(self.browser)
fill_in_standalone_inverter(self.browser, sunshade=True)
submit_standalone_inverter_form(self.browser)
assert_number_of_standalone_inverter_table_rows(self.browser, 2)
edit_standalone_inverter(self.browser, 1)
assert self.browser.find_by_css('#standalone_inverter_form').first.visible
eq_(self.browser.find_by_name('standalone_inverter_id').first.value, saved_id)
eq_(self.browser.find_by_name('standalone_ac_run_length').first.value, '453')
eq_(self.browser.find_by_name('inverter-model').first.value, str(InverterType.SMA.MODEL_12KW.value))
eq_(self.browser.find_by_name('inverter-strings_per_inverter').first.value, '3')
eq_(self.browser.find_by_name('inverter-sunshade').first.checked, False)
eq_(self.browser.find_by_name('inverter-dc_switch').first.checked, True)
self.browser.check('inverter-sunshade')
self.browser.select('inverter-model', InverterType.SMA.MODEL_15KW.value)
self.browser.find_by_value('Save').first.click()
assert_number_of_standalone_inverter_table_rows(self.browser, 2)
assert_standalone_inverter_row(self.browser, 1,
model=InverterType.SMA.MODEL_15KW,
interconnect='Switch Gear',
strings=5,
sunshade=True,
dc_switch=True)
def test_editing_delta_standalone_inverters(self):
select_inverter_brand(self.browser, brand=InverterBrand.DELTA.value)
show_standalone_inverter_form(self.browser)
fill_in_standalone_inverter(self.browser, model=InverterType.DELTA.MODEL_36KW, ac_run_length=453)
submit_standalone_inverter_form(self.browser)
assert_standalone_inverter_row(self.browser, 1,
model=InverterType.DELTA.MODEL_36KW,
interconnect='Switch Gear',
splice_box=True)
saved_id = str(self.test_db_session.query(StandaloneInverter).first().id)
show_standalone_inverter_form(self.browser)
fill_in_standalone_inverter(self.browser, model=InverterType.DELTA.MODEL_60KW, splice_box=False)
submit_standalone_inverter_form(self.browser)
assert_number_of_standalone_inverter_table_rows(self.browser, 2)
edit_standalone_inverter(self.browser, 1)
assert self.browser.find_by_css('#standalone_inverter_form').first.visible
eq_(self.browser.find_by_name('standalone_inverter_id').first.value, saved_id)
eq_(self.browser.find_by_name('standalone_ac_run_length').first.value, '453')
eq_(self.browser.find_by_name('inverter-model').first.value, str(InverterType.DELTA.MODEL_36KW.value))
eq_(self.browser.find_by_name('inverter-splice_box').first.checked, True)
self.browser.uncheck('inverter-splice_box')
self.browser.find_by_value('Save').first.click()
assert_number_of_standalone_inverter_table_rows(self.browser, 2)
assert_standalone_inverter_row(self.browser, 1,
model=InverterType.DELTA.MODEL_36KW,
interconnect='Switch Gear',
splice_box=False)
def test_delta_standalone_inverters_defaults_with_splice_box(self):
pass
def test_deleting_standalone_inverters_from_edit(self):
show_standalone_inverter_form(self.browser)
submit_standalone_inverter_form(self.browser)
edit_standalone_inverter(self.browser, 1)
self.browser.is_text_present('Delete')
self.browser.click_link_by_partial_href('delete_standalone_inverter')
eq_(len(self.browser.find_by_css('.standalone_inverter_table')), 0)
def test_editing_power_monitors(self):
show_power_station_form(self.browser)
fill_in_power_station(self.browser, name='Test Power Station Name', count=1, inverter_count=4)
submit_power_station_form(self.browser)
power_station_id = str(self.test_db_session.query(PowerStation).first().id)
show_monitoring_form(self.browser)
self.browser.select('power_source', power_station_id)
submit_monitoring_form(self.browser)
saved_id = str(self.test_db_session.query(PowerMonitor).first().id)
show_monitoring_form(self.browser)
submit_monitoring_form(self.browser)
assert_supervisor_monitor_row(self.browser, 1, 'Test Power Station Name')
edit_supervisor_monitor(self.browser, 1)
assert self.browser.find_by_css('#supervisor_monitor_form').first.visible
eq_(self.browser.find_by_name('power_source').first.value, power_station_id)
eq_(self.browser.find_by_css('#supervisor_monitor_form #monitor_id').first.value, saved_id)
self.browser.find_by_value('Save').first.click()
assert_number_of_supervisor_monitor_table_rows(self.browser, 2)
assert_supervisor_monitor_row(self.browser, 1, 'Test Power Station Name')
def test_editing_power_station_after_assigning_supervisor_monitor(self):
show_power_station_form(self.browser)
fill_in_power_station(self.browser, name='Test Power Station Name', count=1, inverter_count=4)
submit_power_station_form(self.browser)
power_station_id = str(self.test_db_session.query(PowerStation).first().id)
show_monitoring_form(self.browser)
self.browser.select('power_source', power_station_id)
submit_monitoring_form(self.browser)
edit_power_station(self.browser, 1)
self.browser.find_by_value('Save').first.click()
row = self.browser.find_by_css('.power_station_table tr:nth-child(2) td:nth-child(1)').first
assert "Monitoring" in row.text
def test_deleting_power_monitors_from_edit(self):
show_monitoring_form(self.browser)
submit_monitoring_form(self.browser)
edit_supervisor_monitor(self.browser, 1)
self.browser.is_text_present('Delete')
self.browser.click_link_by_partial_href('delete_supervisor_monitor')
eq_(len(self.browser.find_by_css('.supervisor_monitor_table')), 0)

View File

@@ -0,0 +1,313 @@
import unittest
import mockredis
from splinter import Browser
from helix import main
from helix.constants import redis_constant
from helix.constants import sql_constant
from helix.constants.anchor_type import AnchorType
from helix.constants.inverter_type import InverterType
from helix.constants.module_type import ModuleType
from helix.constants.system_type import SystemType
from helix.models.sql.power_stations import PowerStation
from test.integration.integration_test_helpers import *
from test.test_helpers import assert_error, test_db_session, assert_no_error, \
reset_db_session
class PowerStationConfigurationTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.test_db_session = test_db_session()
@classmethod
def tearDownClass(cls):
cls.test_db_session.close_all()
def setUp(self):
redis_constant.redis_store = mockredis.mock_redis_client()
sql_constant.sql_session_maker = lambda: self.test_db_session
self.app = main.app.test_client()
self.browser = Browser('flask', app=main.app)
self.fill_in_site_characterization_data()
self.browser.visit('/power_station_configuration/')
def tearDown(self):
reset_db_session(self.test_db_session)
def fill_in_site_characterization_data(self,
system_type=SystemType.singleTilt,
module_type=ModuleType.Cell96):
self.browser.visit('/site_characterization/')
self.browser.fill('project_name', 'Test Project Name')
self.browser.fill('building_width', '200')
self.browser.fill('building_height', '30')
self.browser.fill('building_length', '75.5')
self.browser.fill('wind_speed', '125')
self.browser.fill('exposure_category', 'C')
self.browser.fill('ballast_block_weight', '13')
self.browser.fill('building_parapet_height', '0')
self.browser.fill('max_system_pressure', '10')
self.browser.fill('system_type', system_type.value)
self.browser.fill('anchor_type', AnchorType.EcoFasten.value)
self.browser.fill('module_type', module_type.value)
self.browser.fill('design_spectral_response', '1.02')
self.browser.find_by_value('Next').first.click()
self.browser.visit('/power_station_configuration/')
def test_entering_power_station_data_shows_entered_values_in_table(self):
fill_in_power_station(self.browser, name='Test Power Station Name', inverter_count=3, inverters=[
{
'model': InverterType.SMA.MODEL_15KW,
'strings': '3',
},
{
'model': InverterType.SMA.MODEL_12KW,
'strings': '4',
},
{
'model': InverterType.SMA.MODEL_24KW,
'strings': '7',
}
])
submit_power_station_form(self.browser)
assert_number_of_power_station_table_rows(self.browser, 1)
models = ['15kW SMA Tripower - 514687', '12kW SMA Tripower - 514686', '24kW SMA Tripower - 514685']
strings = ['3', '4', '7']
assert_power_station_table_row(self.browser, 1, name='Test Power Station Name', models=models, strings=strings,
quantity=1)
eq_(self.browser.find_by_name('power_station_description').first.value, 'Power Station 2')
def test_clicking_back_goes_to_summary_table(self):
self.browser.click_link_by_partial_text('Back')
assert_step_is_active(self.browser, 3)
def test_all_power_station_values_are_required(self):
fill_in_power_station(self.browser, name='', count='', run_length='')
submit_power_station_form(self.browser)
assert_error(self.browser, 'power_station_description', 'Power Station Description is required.')
assert_error(self.browser, 'power_station_quantity', 'Not a valid integer value')
assert_error(self.browser, 'ac_run_length', 'Not a valid integer value')
eq_(len(self.browser.find_by_css('#power_station_form.hidden')), 0)
def test_ac_run_length_must_be_non_negative(self):
fill_in_power_station(self.browser, run_length=-5)
submit_power_station_form(self.browser)
assert_error(self.browser, 'ac_run_length', 'Number must be at least 0.')
eq_(len(self.browser.find_by_css('#power_station_form.hidden')), 0)
def test_ac_run_length_defaults_to_zero(self):
eq_(self.browser.find_by_name('ac_run_length').first.value, '0')
def test_power_station_quantity_must_be_non_negative(self):
fill_in_power_station(self.browser, count=-2)
submit_power_station_form(self.browser)
assert_error(self.browser, 'power_station_quantity', 'Number must be at least 0.')
eq_(len(self.browser.find_by_css('#power_station_form.hidden')), 0)
def test_power_station_description_defaults_to_power_station_1(self):
eq_(self.browser.find_by_name('power_station_description').first.value, 'Power Station 1')
def test_default_inverter_type_is_24kW(self):
eq_(self.browser.find_by_name('inverter_1-model').first.value, str(InverterType.SMA.MODEL_24KW.value))
eq_(self.browser.find_by_name('inverter_2-model').first.value, str(InverterType.SMA.MODEL_24KW.value))
eq_(self.browser.find_by_name('inverter_3-model').first.value, str(InverterType.SMA.MODEL_24KW.value))
eq_(self.browser.find_by_name('inverter_4-model').first.value, str(InverterType.SMA.MODEL_24KW.value))
def test_clicking_trash_can_deletes_power_station(self):
fill_in_power_station(self.browser, name='Fake PS ONE')
submit_power_station_form(self.browser)
fill_in_power_station(self.browser, name='Fake PS TWO')
submit_power_station_form(self.browser)
assert_number_of_power_station_table_rows(self.browser, 2)
self.browser.click_link_by_partial_href('delete_power_station')
assert_number_of_power_station_table_rows(self.browser, 1)
assert not self.browser.is_text_present('Fake PS ONE')
assert self.browser.is_text_present('Fake PS TWO')
self.browser.click_link_by_partial_href('delete_power_station')
eq_(len(self.browser.find_by_css('.power_station_table')), 0)
def test_standalone_inverter_form_does_not_show_power_stations_with_more_than_one_in_group(self):
fill_in_power_station(self.browser, name='Fake PS ONE', inverter_count=3, count=1)
submit_power_station_form(self.browser)
submit_standalone_inverter_form(self.browser)
fill_in_power_station(self.browser, name='Fake PS TWO', inverter_count=3, count=2)
submit_power_station_form(self.browser)
attachment_point_options = self.browser.find_by_css('#attachment_point option')
eq_(len(attachment_point_options), 2)
attachment_options_as_list = list(map(lambda x: x.text, attachment_point_options))
eq_(attachment_options_as_list, ['Switch Gear', 'Fake PS ONE'])
def test_standalone_inverter_form_only_shows_power_stations_with_open_spots(self):
fill_in_power_station(self.browser, name='Fake PS ONE', inverter_count=2, count=1)
submit_power_station_form(self.browser)
fill_in_power_station(self.browser, name='Fake PS TWO', inverter_count=4, count=1)
submit_power_station_form(self.browser)
attachment_point_options = self.browser.find_by_css('#attachment_point option')
eq_(len(attachment_point_options), 2)
attachment_options_as_list = list(map(lambda x: x.text, attachment_point_options))
eq_(attachment_options_as_list, ['Switch Gear', 'Fake PS ONE'])
power_station_id = str(self.test_db_session.query(PowerStation).first().id)
self.browser.fill('attachment_point', power_station_id)
submit_standalone_inverter_form(self.browser)
attachment_point_options = self.browser.find_by_css('#attachment_point option')
eq_(len(attachment_point_options), 2)
attachment_options_as_list = list(map(lambda x: x.text, attachment_point_options))
eq_(attachment_options_as_list, ['Switch Gear', 'Fake PS ONE'])
self.browser.fill('attachment_point', power_station_id)
submit_standalone_inverter_form(self.browser)
attachment_point_options = self.browser.find_by_css('#attachment_point option')
eq_(len(attachment_point_options), 1)
attachment_options_as_list = list(map(lambda x: x.text, attachment_point_options))
eq_(attachment_options_as_list, ['Switch Gear'])
def test_standalone_inverter_quantity_creates_multiple_inverters(self):
fill_in_standalone_inverter(self.browser, quantity=5, model=InverterType.SMA.MODEL_15KW, strings=3, sunshade=True, dc_switch=True)
submit_standalone_inverter_form(self.browser)
assert_number_of_standalone_inverter_table_rows(self.browser, 5)
assert_standalone_inverter_row(self.browser, 1, model=InverterType.SMA.MODEL_15KW, strings=3, sunshade=True, dc_switch=True)
assert_standalone_inverter_row(self.browser, 2, model=InverterType.SMA.MODEL_15KW, strings=3, sunshade=True, dc_switch=True)
assert_standalone_inverter_row(self.browser, 3, model=InverterType.SMA.MODEL_15KW, strings=3, sunshade=True, dc_switch=True)
assert_standalone_inverter_row(self.browser, 4, model=InverterType.SMA.MODEL_15KW, strings=3, sunshade=True, dc_switch=True)
assert_standalone_inverter_row(self.browser, 5, model=InverterType.SMA.MODEL_15KW, strings=3, sunshade=True, dc_switch=True)
def test_entering_standalone_inverter_data_shows_entered_values_in_table_and_hides_form(self):
submit_standalone_inverter_form(self.browser)
fill_in_power_station(self.browser, name='Fake PS ONE', inverter_count=3)
submit_power_station_form(self.browser)
fill_in_standalone_inverter(self.browser, model=InverterType.SMA.MODEL_15KW, strings=3, sunshade=True, dc_switch=True)
power_station_id = str(self.test_db_session.query(PowerStation).first().id)
self.browser.fill('attachment_point', power_station_id)
submit_standalone_inverter_form(self.browser)
assert_number_of_standalone_inverter_table_rows(self.browser, 2)
assert_standalone_inverter_row(self.browser, 1,
model=InverterType.SMA.MODEL_24KW,
interconnect='Switch Gear',
strings=8,
dc_switch=False,
sunshade=False)
assert_standalone_inverter_row(self.browser, 2,
model=InverterType.SMA.MODEL_15KW,
interconnect='Fake PS ONE',
strings=3,
dc_switch=True,
sunshade=True)
assert len(self.browser.find_by_css('#standalone_inverter_form.hidden')) == 1
def test_standalone_inverter_ac_run_length_must_be_non_negative(self):
fill_in_standalone_inverter(self.browser, ac_run_length=-5)
submit_standalone_inverter_form(self.browser)
assert_error(self.browser, 'standalone_ac_run_length', 'Number must be at least 0.')
eq_(len(self.browser.find_by_css('#standalone_inverter_form.hidden')), 0)
def test_standalone_inverter_ac_run_length_defaults_to_zero(self):
eq_(self.browser.find_by_name('standalone_ac_run_length').first.value, '0')
def test_clicking_trash_can_deletes_standalone_inverter(self):
eq_(len(self.browser.find_by_css('.standalone_inverter_table')), 0)
submit_standalone_inverter_form(self.browser)
assert_no_error(self.browser, 'attachment_point')
assert_number_of_standalone_inverter_table_rows(self.browser, 1)
fill_in_power_station(self.browser, name='Fake PS ONE', count=1, inverter_count=3)
submit_power_station_form(self.browser)
power_station_id = str(self.test_db_session.query(PowerStation).first().id)
self.browser.fill('attachment_point', power_station_id)
submit_standalone_inverter_form(self.browser)
assert_number_of_standalone_inverter_table_rows(self.browser, 2)
self.browser.click_link_by_partial_href('delete_standalone_inverter')
assert_number_of_standalone_inverter_table_rows(self.browser, 1)
assert_standalone_inverter_row(self.browser, 1, interconnect='Fake PS ONE')
self.browser.click_link_by_partial_href('delete_standalone_inverter')
eq_(len(self.browser.find_by_css('.standalone_inverter_table')), 0)
def test_supervisor_monitoring_form_does_not_show_power_stations_that_already_have_monitoring(self):
fill_in_power_station(self.browser, name='Fake PS One')
submit_power_station_form(self.browser)
power_source_options = self.browser.find_by_css('#power_source option')
eq_(len(power_source_options), 2)
power_source_options_as_list = list(map(lambda x: x.text, power_source_options))
eq_(power_source_options_as_list, ['Switch Gear/External', 'Fake PS One'])
power_station_id = str(self.test_db_session.query(PowerStation).first().id)
fill_in_monitoring_form(self.browser, power_station_id)
submit_monitoring_form(self.browser)
power_source_options = self.browser.find_by_css('#power_source option')
eq_(len(power_source_options), 1)
power_source_options_as_list = list(map(lambda x: x.text, power_source_options))
eq_(power_source_options_as_list, ['Switch Gear/External'])
def test_adding_supervisor_monitoring_to_power_station_shows_monitoring_label_on_power_station_table(self):
fill_in_power_station(self.browser, name='Fake PS One')
submit_power_station_form(self.browser)
power_station_id = str(self.test_db_session.query(PowerStation).first().id)
fill_in_monitoring_form(self.browser, power_station_id)
submit_monitoring_form(self.browser)
row = self.browser.find_by_css('.power_station_table tr:nth-child(2) td:nth-child(1)').first
assert "Monitoring" in row.text, "Expected \n'%s'\n to contain 'Monitoring'" % row.text
def test_entering_supervisor_monitoring_data_shows_entered_values_in_table_and_hides_form(self):
fill_in_monitoring_form(self.browser, 'switch_gear')
submit_monitoring_form(self.browser)
assert_number_of_supervisor_monitor_table_rows(self.browser, 1)
assert_supervisor_monitor_row(self.browser, 1, 'Switch Gear/External')
eq_(len(self.browser.find_by_css('#supervisor_monitor_form.hidden')), 1)
def test_clicking_trash_can_deletes_supervisor_monitor(self):
fill_in_monitoring_form(self.browser, 'switch_gear')
submit_monitoring_form(self.browser)
fill_in_power_station(self.browser, name='Fake PS ONE')
submit_power_station_form(self.browser)
power_station_id = str(self.test_db_session.query(PowerStation).first().id)
fill_in_monitoring_form(self.browser, power_station_id)
submit_monitoring_form(self.browser)
assert_number_of_supervisor_monitor_table_rows(self.browser, 2)
self.browser.click_link_by_partial_href('delete_supervisor_monitor')
assert_number_of_supervisor_monitor_table_rows(self.browser, 1)
assert_supervisor_monitor_row(self.browser, 1, 'Fake PS ONE')
self.browser.click_link_by_partial_href('delete_supervisor_monitor')
eq_(len(self.browser.find_by_css('.supervisor_monitor_table')), 0)

View File

@@ -0,0 +1,58 @@
from flask.ext.testing import LiveServerTestCase
import mockredis
from splinter import Browser
from helix import main
from helix.constants import redis_constant
from helix.constants import sql_constant
from helix.db.sql_manager import SQLManager
from test.test_helpers import test_db_session, reset_db_session
class SiteCharacterizationDynamicFormTestCase(LiveServerTestCase):
def create_app(self):
redis_constant.redis_store = mockredis.mock_redis_client()
sql_constant.sql_session_maker = lambda: SQLManager.get_sql_session_maker('postgres://pivotal:@localhost/test')
app = main.app
app.config['TESTING'] = True
app.config['LIVESERVER_PORT'] = 8943
return app
@classmethod
def setUpClass(cls):
cls.test_db_session = test_db_session()
cls.browser = Browser('phantomjs')
@classmethod
def tearDownClass(cls):
cls.test_db_session.close_all()
cls.browser.quit()
def setUp(self):
self.browser.visit(self.get_server_url() + '/site_characterization/')
def tearDown(self):
reset_db_session(self.test_db_session)
self.browser.cookies.delete()
def test_selecting_exposure_B_to_C_shows_transition_distance(self):
assert not self.browser.is_text_present("Exposure Transition Distance")
self.browser.select('exposure_category', 'B to C')
assert self.browser.is_text_present("Exposure Transition Distance")
self.browser.select('exposure_category', 'D')
assert not self.browser.is_text_present("Exposure Transition Distance")
def test_showing_and_hiding_tooltip_for_anchor_type(self):
assert not self.browser.is_text_present('OMG anchors are compatible with TPO and PVC roof membranes.')
self.browser.find_by_css('#anchor_type_row i.icon-info-circled').first.click()
assert self.browser.is_text_present('OMG anchors are compatible with TPO and PVC roof membranes.')
self.browser.find_by_css('body').first.click()
assert not self.browser.is_text_present('OMG anchors are compatible with TPO and PVC roof membranes.')
def test_showing_and_hiding_tooltip_for_importance_factor(self):
assert not self.browser.is_text_present('Use 1.5 for essential facilities such as: Hospitals, Police, Fire & Rescue stations & Designated emergency shelters. All other structures should use 1.0.')
self.browser.find_by_css('#importance_factor_row i.icon-info-circled').first.click()
assert self.browser.is_text_present('Use 1.5 for essential facilities such as: Hospitals, Police, Fire & Rescue stations & Designated emergency shelters. All other structures should use 1.0.')
self.browser.find_by_css('body').first.click()
assert not self.browser.is_text_present('Use 1.5 for essential facilities such as: Hospitals, Police, Fire & Rescue stations & Designated emergency shelters. All other structures should use 1.0.')

View File

@@ -0,0 +1,229 @@
import unittest
import mockredis
from nose.tools import eq_
from splinter import Browser
from helix import main
from helix.constants import redis_constant
from helix.constants import sql_constant
from helix.constants.anchor_type import AnchorType
from helix.constants.system_type import SystemType
from helix.constants.module_type import ModuleType
from helix.models.sql.inverters import Inverter
from helix.models.sql.power_monitors import PowerMonitor
from helix.models.sql.power_stations import PowerStation
from helix.models.sql.sites import Site
from helix.models.sql.standalone_inverters import StandaloneInverter
from helix.models.sql.users import User
from test.test_helpers import assert_error, assert_no_error, test_db_session, reset_db_session
class SiteCharacterizationTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.test_db_session = test_db_session()
@classmethod
def tearDownClass(cls):
cls.test_db_session.close_all()
def setUp(self):
redis_constant.redis_store = mockredis.mock_redis_client()
sql_constant.sql_session_maker = lambda: self.test_db_session
self.app = main.app.test_client()
self.browser = Browser('flask', app=main.app)
self.browser.visit('/site_characterization')
def tearDown(self):
self.test_db_session.rollback()
reset_db_session(self.test_db_session)
def fill_in_site_characterization_data(self, module_type=ModuleType.Cell96):
self.browser.fill('project_name', 'Test Project Name')
self.browser.fill('building_width', '200')
self.browser.fill('building_height', '30')
self.browser.fill('building_length', '75.5')
self.browser.fill('wind_speed', '125')
self.browser.fill('exposure_category', 'C')
self.browser.fill('ballast_block_weight', '13')
self.browser.fill('building_parapet_height', '0')
self.browser.fill('max_system_pressure', '10')
self.browser.fill('module_type', module_type.value)
self.browser.fill('system_type', SystemType.singleTilt.value)
self.browser.fill('anchor_type', AnchorType.EcoFasten.value)
self.browser.fill('importance_factor', '1.5')
self.browser.fill('design_spectral_response', '1')
def test_page_displays_welcome_text(self):
assert self.browser.is_text_present("Helix Calculator")
def test_navigation_header_shows_user_on_first_step(self):
assert self.browser.find_by_css('.navigation_header #step_1').has_class('active')
assert not self.browser.find_by_css('.navigation_header #step_1').has_class('completed')
def test_completing_site_characterization_step_updates_navigation_header(self):
self.fill_in_site_characterization_data()
self.browser.find_by_value('Next').first.click()
assert self.browser.find_by_css('.navigation_header #step_2').has_class('active')
assert not self.browser.find_by_css('.navigation_header #step_1').has_class('active')
assert self.browser.find_by_css('.navigation_header #step_1').has_class('completed')
self.browser.click_link_by_partial_text("Back")
assert not self.browser.find_by_css('.navigation_header #step_2').has_class('active')
assert self.browser.find_by_css('.navigation_header #step_1').has_class('active')
assert self.browser.find_by_css('.navigation_header #step_1').has_class('completed')
def test_entering_site_characterization_data_saves_entered_values(self):
self.fill_in_site_characterization_data() # TODO: Fill in to change to 128
self.browser.find_by_value('Next').first.click()
self.browser.click_link_by_partial_text("Back")
eq_(self.browser.find_by_name('project_name').first.value, 'Test Project Name')
eq_(self.browser.find_by_name('building_width').first.value, '200.0')
eq_(self.browser.find_by_name('building_height').first.value, '30.0')
eq_(self.browser.find_by_name('building_length').first.value, '75.5')
eq_(self.browser.find_by_name('wind_speed').first.value, '125')
eq_(self.browser.find_by_name('exposure_category').first.value, 'C')
eq_(self.browser.find_by_name('ballast_block_weight').first.value, '13.0')
eq_(self.browser.find_by_name('building_parapet_height').first.value, '0.0')
eq_(self.browser.find_by_name('max_system_pressure').first.value, '10.0')
eq_(self.browser.find_by_name('module_type').first.value, ModuleType.Cell96.value)
eq_(self.browser.find_by_name('system_type').first.value, SystemType.singleTilt.value)
eq_(self.browser.find_by_name('anchor_type').first.value, AnchorType.EcoFasten.value)
eq_(self.browser.find_by_name('importance_factor').first.value, '1.5')
eq_(self.browser.find_by_name('design_spectral_response').first.value, '1.0')
def test_form_shows_wind_speed_link(self):
info_link = self.browser.find_by_css('#wind_speed_row a').first
eq_(info_link.text, 'Look up')
eq_(info_link['href'], 'http://windspeed.atcouncil.org/')
def test_form_shows_seismic_link(self):
info_link = self.browser.find_by_css('#design_spectral_response_row a').first
eq_(info_link.text, 'Look up')
eq_(info_link['href'], 'http://earthquake.usgs.gov/designmaps/us/application.php')
def test_form_shows_exposure_category_link(self):
info_link = self.browser.find_by_css('#exposure_category_row a').first
eq_(info_link.text, 'More info')
info_link.click()
assert self.browser.is_text_present("Urban and suburban areas, wooded areas")
def test_all_site_charactarization_values_are_required(self):
self.browser.fill('ballast_block_weight', '')
self.browser.fill('max_system_pressure', '')
self.browser.find_by_value('Next').first.click()
assert_error(self.browser, 'project_name', 'Project Name is required.')
assert_error(self.browser, 'building_height', 'Not a valid decimal value')
assert_error(self.browser, 'building_width', 'Not a valid decimal value')
assert_error(self.browser, 'building_length', 'Not a valid decimal value')
assert_error(self.browser, 'wind_speed', 'Not a valid integer value')
assert_error(self.browser, 'ballast_block_weight', 'Not a valid decimal value')
assert_error(self.browser, 'building_parapet_height', 'Not a valid decimal value')
assert_error(self.browser, 'max_system_pressure', 'Not a valid decimal value')
assert_error(self.browser, 'design_spectral_response', 'Not a valid decimal value')
def test_characterization_values_not_overwritten_when_Nextting_invalid_request(self):
self.browser.fill('project_name', '')
self.browser.fill('building_width', '-1')
self.browser.fill('building_height', '-10')
self.browser.fill('building_length', 'hi')
self.browser.fill('wind_speed', '400')
self.browser.fill('exposure_category', 'D')
self.browser.fill('ballast_block_weight', '40')
self.browser.fill('building_parapet_height', '-1')
self.browser.fill('max_system_pressure', '0')
self.browser.fill('system_type', SystemType.dualTilt.value)
self.browser.fill('module_type', ModuleType.Cell128.value)
self.browser.fill('anchor_type', AnchorType.OMG_PowerGrip.value)
self.browser.fill('design_spectral_response', '-2')
self.browser.find_by_value('Next').first.click()
eq_(self.browser.find_by_name('project_name').first.value, '')
eq_(self.browser.find_by_name('building_width').first.value, '-1')
eq_(self.browser.find_by_name('building_height').first.value, '-10')
eq_(self.browser.find_by_name('building_length').first.value, 'hi')
eq_(self.browser.find_by_name('wind_speed').first.value, '400')
eq_(self.browser.find_by_name('exposure_category').first.value, 'D')
eq_(self.browser.find_by_name('ballast_block_weight').first.value, '40')
eq_(self.browser.find_by_name('building_parapet_height').first.value, '-1')
eq_(self.browser.find_by_name('max_system_pressure').first.value, '0')
eq_(self.browser.find_by_name('system_type').first.value, SystemType.dualTilt.value)
eq_(self.browser.find_by_name('module_type').first.value, ModuleType.Cell128.value)
eq_(self.browser.find_by_name('anchor_type').first.value, AnchorType.OMG_PowerGrip.value)
eq_(self.browser.find_by_name('design_spectral_response').first.value, '-2')
def test_wind_speed_value_must_be_in_range(self):
self.browser.fill('wind_speed', '90')
self.browser.find_by_value('Next').first.click()
assert_error(self.browser, 'wind_speed', 'Number must be between 100 and 200.')
def test_ballast_block_weight_value_must_be_in_range(self):
self.browser.fill('ballast_block_weight', '90')
self.browser.find_by_value('Next').first.click()
assert_error(self.browser, 'ballast_block_weight', 'Number must be between 12 and 20.')
def test_building_dimensions_must_be_non_negative(self):
self.browser.fill('building_width', '-4')
self.browser.fill('building_height', '-10')
self.browser.fill('building_length', '-1')
self.browser.fill('building_parapet_height', '-3')
self.browser.find_by_value('Next').first.click()
assert_error(self.browser, 'building_height', 'Number must be at least 0.')
assert_error(self.browser, 'building_width', 'Number must be at least 0.')
assert_error(self.browser, 'building_length', 'Number must be at least 0.')
assert_error(self.browser, 'building_parapet_height', 'Number must be at least 0.')
def test_transition_distance_must_be_filled_out_for_interpolating_exposure_categories(self):
self.browser.fill('exposure_category', 'B to C')
self.browser.find_by_value('Next').first.click()
assert_error(self.browser, 'exposure_category_transition_distance', 'Number must be at least 1.')
def test_transition_distance_must_be_greater_than_0_for_interpolating_exposure_categories(self):
self.browser.fill('exposure_category', 'B to C')
self.browser.fill('exposure_category_transition_distance', '-10')
self.browser.find_by_value('Next').first.click()
assert_error(self.browser, 'exposure_category_transition_distance', 'Number must be at least 1.')
def test_transition_distance_standard_categories_does_not_need_to_be_filled_out(self):
self.browser.fill('exposure_category', 'B')
self.browser.find_by_value('Next').first.click()
assert_no_error(self.browser, 'exposure_category_transition_distance')
def test_max_system_pressure_must_be_non_negative(self):
self.browser.fill('max_system_pressure', '-2')
self.browser.find_by_value('Next').first.click()
assert_error(self.browser, 'max_system_pressure', 'Number must be at least 0.')
def test_design_spectral_response_must_be_in_range(self):
self.browser.fill('design_spectral_response', '-1')
self.browser.find_by_value('Next').first.click()
assert_error(self.browser, 'design_spectral_response', 'Number must be between 0 and 5.')
def test_building_dimensions_allow_zero(self):
self.browser.fill('building_width', '0')
self.browser.fill('building_height', '0')
self.browser.fill('building_length', '0')
self.browser.find_by_value('Next').first.click()
assert_no_error(self.browser, 'building_width')
assert_no_error(self.browser, 'building_height')
assert_no_error(self.browser, 'building_length')
def test_ballast_block_weight_defaults_to_14(self):
eq_(self.browser.find_by_name('ballast_block_weight').first.value, '14.0')
def test_max_system_pressure_defaults_to_10(self):
eq_(self.browser.find_by_name('max_system_pressure').first.value, '12.0')
def test_system_type_defaults_to_dual_tilt(self):
eq_(self.browser.find_by_name('system_type').first.value, SystemType.dualTilt.value)
def test_module_type_defaults_to_96_cell(self):
eq_(self.browser.find_by_name('module_type').first.value, ModuleType.Cell96.value)
def test_exposure_transition_distance_defaults_to_zero(self):
eq_(self.browser.find_by_name('exposure_category_transition_distance').first.value, '0')

View File

@@ -0,0 +1,127 @@
import unittest
from helix.constants.module_type import ModuleType
import mockredis
from splinter import Browser
from helix import main
from helix.constants import redis_constant, sql_constant
from helix.constants.system_type import SystemType
from helix.constants.anchor_type import AnchorType
from helix.models.panel import PanelWarnings
from test.integration.integration_test_helpers import assert_step_is_active, assert_step_is_completed
from test.test_helpers import *
class SiteSummaryTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.test_db_session = test_db_session()
@classmethod
def tearDownClass(cls):
cls.test_db_session.close_all()
def setUp(self):
redis_constant.redis_store = mockredis.mock_redis_client()
sql_constant.sql_session_maker = lambda: self.test_db_session
self.app = main.app.test_client()
self.browser = Browser('flask', app=main.app)
self.browser.visit('/summary/')
def tearDown(self):
reset_db_session(self.test_db_session)
def fill_in_site_characterization_data(self, system_type=SystemType.singleTilt, module_type=ModuleType.Cell96, ul_warning=False):
self.browser.visit('/site_characterization/')
self.browser.fill('project_name', 'Test Project Name')
self.browser.fill('building_width', '200')
self.browser.fill('building_height', '30.254')
self.browser.fill('building_length', '75.5')
self.browser.fill('wind_speed', '125' if not ul_warning else '160')
self.browser.fill('exposure_category', 'C')
self.browser.fill('ballast_block_weight', '13')
self.browser.fill('building_parapet_height', '0')
self.browser.fill('max_system_pressure', '10')
self.browser.fill('system_type', system_type.value)
self.browser.fill('anchor_type', AnchorType.EcoFasten.value)
self.browser.fill('module_type', module_type.value)
self.browser.fill('design_spectral_response', '1.02')
self.browser.find_by_value('Next').first.click()
self.browser.visit('/summary/')
def assert_summary_values_column_data(self, column, label, value):
eq_(self.browser.find_by_css('#summary_values_table tr:nth-child(1) th:nth-child(%d)' % column).first.html,
label)
eq_(self.browser.find_by_css('#summary_values_table tr:nth-child(2) td:nth-child(%d)' % column).first.text,
value)
def test_shows_summary_table_if_user_has_uploaded_data(self):
self.fill_in_site_characterization_data()
assert len(self.browser.find_by_css("#summary_table")) > 0
eq_(len(self.browser.find_by_css("#summary_table tr.alternating_row_color")), 13)
def test_does_not_show_tables_if_user_has_not_uploaded_site_data(self):
assert len(self.browser.find_by_css("#summary_table")) == 0
def test_viewing_summary_table_without_site_characteristics_marks_as_incomplete(self):
assert_step_is_active(self.browser, 2, True)
assert_step_is_completed(self.browser, 2, False)
self.fill_in_site_characterization_data()
assert_step_is_active(self.browser, 2, True)
assert_step_is_completed(self.browser, 2, True)
def test_clicking_next_goes_to_array_summary(self):
self.fill_in_site_characterization_data()
self.browser.click_link_by_partial_text('Next')
assert_step_is_active(self.browser, 3)
assert_step_is_active(self.browser, 2, False)
def test_shows_summary_values(self):
self.fill_in_site_characterization_data()
self.assert_summary_values_column_data(1, 'L<sub>B</sub> - Building Scaling Factor (feet)', '30.25')
self.assert_summary_values_column_data(2, 'K<sub>Z</sub> - Site Wind Pressure Factor', '0.98')
self.assert_summary_values_column_data(3, 'q<sub>z</sub> - Site Wind Pressure (psf)', '33.46')
def test_clicking_back_goes_to_site_characterization(self):
self.browser.click_link_by_partial_text('Back')
assert_step_is_active(self.browser, 1)
def test_shows_minimum_array_size_single_tilt(self):
self.fill_in_site_characterization_data()
eq_(len(self.browser.find_by_css("#summary_table tr:last_child td")), 12)
eq_(self.browser.find_by_css("#summary_table tr:last_child td:first_child").first.text,
"Minimum Array Size by Zone")
def test_shows_minimum_array_size_dual_tilt(self):
self.fill_in_site_characterization_data(system_type=SystemType.dualTilt)
eq_(len(self.browser.find_by_css("#summary_table tr:last_child td")), 6)
eq_(self.browser.find_by_css("#summary_table tr:last_child td:first_child").first.text,
"Minimum Array Size by Zone (Tents)")
def test_shows_dual_tilt_images_for_dual_tilt_system(self):
self.fill_in_site_characterization_data(system_type=SystemType.dualTilt)
eq_(len(self.browser.find_by_css('.image_container a')), 2)
assert 'panel_types_dual_tilt' in self.browser.find_by_css('.image_container a:nth-child(1)').first['data-featherlight']
assert 'wind_zones_dual_tilt' in self.browser.find_by_css('.image_container a:nth-child(2)').first['data-featherlight']
def test_shows_single_tilt_images_for_single_tilt_system(self):
self.fill_in_site_characterization_data(system_type=SystemType.singleTilt)
eq_(len(self.browser.find_by_css('.image_container a')), 3)
assert 'panel_types_single_tilt' in self.browser.find_by_css('.image_container a:nth-child(1)').first['data-featherlight']
assert 'wind_zones_single_tilt_north' in self.browser.find_by_css('.image_container a:nth-child(2)').first['data-featherlight']
assert 'wind_zones_single_tilt_south' in self.browser.find_by_css('.image_container a:nth-child(3)').first['data-featherlight']
def test_shows_warning_message(self):
self.fill_in_site_characterization_data(ul_warning=True)
summary_warning_selector = self.browser.find_by_css('.summary_warning')
eq_(len(summary_warning_selector), 1)
summary_warning_message = summary_warning_selector.first.text
eq_(summary_warning_message, PanelWarnings.MaxPsf.value)
def test_shows_cells_with_warning_with_different_style(self):
self.fill_in_site_characterization_data(ul_warning=True)
eq_(len(self.browser.find_by_css('.table_horizontal_borders.warning')), 3)
assert 'warning' in self.browser.find_by_css('#summary_table tr:nth-child(3) td:nth-child(3)').first['class']
assert 'warning' in self.browser.find_by_css('#summary_table tr:nth-child(4) td:nth-child(2)').first['class']
assert 'warning' in self.browser.find_by_css('#summary_table tr:nth-child(5) td:nth-child(2)').first['class']