def test_derive_duration_string_returns_minutes_seconds_repr_of_timedelta( self, minutes, seconds): duration = timedelta(minutes=minutes, seconds=seconds) ret_val = utils.derive_duration_string(duration) assert ret_val == f'{minutes:02}:{seconds:02}'
def form_has_values_from_watering_station(self, watering_station): return all([ self.status == watering_station.status, self.plant_type == watering_station.plant_type, self.watering_duration == derive_duration_string( watering_station.watering_duration), self.moisture_threshold == str(watering_station.moisture_threshold) ])
def form_fields(cls, **kwargs): keys = [ 'moisture_threshold', 'watering_duration', 'plant_type', 'status' ] data = super().json() form_data = {key: data[key] for key in keys} form_data['watering_duration'] = derive_duration_string( form_data['watering_duration']) return form_data
def test_POST_with_valid_data_creates_new_watering_station_with_supplied_data( self, auth_client, watering_station_form_fields): auth_client.post(self.url, data=watering_station_form_fields) self.garden.refresh_from_db() watering_station = self.garden.watering_stations.last() assert watering_station_form_fields.pop( 'watering_duration') == derive_duration_string( watering_station.watering_duration) assertions.assert_model_fields_have_values( watering_station_form_fields, watering_station)
def test_POST_with_valid_data_updates_the_watering_station_with_given_data( self, auth_client, watering_station_form_fields): resp = auth_client.post(self.url, data=watering_station_form_fields) self.watering_station.refresh_from_db() assert resp.status_code == status.HTTP_200_OK assert self.watering_station.moisture_threshold == watering_station_form_fields[ 'moisture_threshold'] assert derive_duration_string( self.watering_station.watering_duration ) == watering_station_form_fields['watering_duration'] assert self.watering_station.plant_type == watering_station_form_fields[ 'plant_type']
def test_POST_doesnt_update_watering_station_is_accessed_by_user_who_doesnt_own_it( self, auth_client, watering_station, watering_station_form_fields): url = self.create_url(watering_station.garden.pk, watering_station.pk) auth_client.post(url, data=watering_station_form_fields) watering_station.refresh_from_db() assert any([ watering_station.moisture_threshold != watering_station_form_fields['moisture_threshold'], derive_duration_string(watering_station.watering_duration) != watering_station_form_fields['watering_duration'], watering_station.plant_type != watering_station_form_fields['plant_type'] ])
def test_user_can_create_a_garden(self): # a user goes to the garden page self.driver.get(self.url) list_page = GardenListPage(self.driver) self.wait_for_page_to_be_loaded(list_page) # they see a list of registered gardens which is currently 0 assert list_page.get_number_of_gardens() == 0 # they also see an option to create a new garden, which they click and immediately see a modal that prompts # them for the garden name. They are also prompted for the number of watering stations that are going to be in # this garden, an image, and the update interval list_page.new_garden_button.click() self.wait_for_modal_to_be_visible(list_page.modal_id) assert list_page.new_garden_name == '' assert list_page.num_watering_stations == '' assert list_page.update_frequency == str( derive_duration_string(_default_update_frequency())) assert list_page.garden_image # they cancel out of the modal, but then re-enter again list_page.cancel_button.click() self.wait_for_model_to_disappear(list_page.modal_id) list_page.new_garden_button.click() self.wait_for_modal_to_be_visible(list_page.modal_id) # they enter a negative number for the number of watering stations and hit enter, and they see a error message # appear garden_name = 'My New Garden' list_page.new_garden_name = garden_name list_page.num_watering_stations = -1 list_page.update_frequency = -1 list_page.submit_button.click() self.wait_for_form_error('error_1_id_num_watering_stations') self.wait_for_form_error('error_1_id_update_frequency') # now they enter a garden name and valid number of watering stations and see a new garden appear in the list of # gardens num_watering_stations = 3 garden_image = 'test_garden_image.png' update_frequency = '0:10:00' list_page.num_watering_stations = num_watering_stations list_page.update_frequency = update_frequency self.perform_image_crop(list_page, garden_image) list_page.submit_button.click() list_page.wait_for_garden_in_list(garden_name) assert_image_files_equal(list_page.get_garden_image(garden_name), garden_image) # they click on the garden and are taken to the associated garden page list_page.click_garden(garden_name) detail_page = GardenDetailPage(self.driver) self.wait_for_page_to_be_loaded(detail_page) # they see the watering stations that are part of the garden assert detail_page.get_number_watering_stations( ) == num_watering_stations # the user then clicks the AutoGarden tag in the nav bar to navigate home and is taken back to the garden # list page list_page.home_button.click() self.wait_for_page_to_be_loaded(list_page)
def test_microcontroller_interaction_with_server(self): # the microcontroller PATCHs data to the garden api to update the garden instance garden_data = { 'water_level': Garden.LOW, 'connection_strength': random.randint(-100, 0) } self.send_patch_request_to_garden_api(self.garden, garden_data) # afterwards a user visits the garden detail page where they see that the garden info has updated self.driver.get(self.url) detail_gpage = GardenDetailPage(self.driver) self.wait_for_page_to_be_loaded(detail_gpage) self.garden.refresh_from_db() assert detail_gpage.is_displaying_info_for_garden(self.garden) # the MC sends a GET request to retrieve the watering station configs from the server self.send_get_request_to_watering_station_api(self.garden) # immediately after the MC sends a GET request to retrieve the garden update interval duration self.send_get_request_to_garden_api(self.garden) # the MC then performs its operations and POSTs the data from its watering station sensors to the server data = [] for i in range(self.num_watering_stations): data.append({'moisture_level': random.uniform(0, 100)}) self.send_post_request_to_watering_station_api(self.garden, data) # the microcontroller then crashes and misses an update. The user refreshes the page and sees the updated # connection info, where the connection status is not disconnected sleep(self.update_frequency) self.driver.get(self.url) self.wait_for_page_to_be_loaded(detail_gpage) self.garden.refresh_from_db() assert detail_gpage.is_displaying_info_for_garden(self.garden) assert self.garden.is_connected == False # the user then change the garden configs detail_gpage.edit_button.click() update_gpage = GardenUpdatePage(self.driver) self.wait_for_page_to_be_loaded(update_gpage) update_frequency = timedelta(minutes=7, seconds=20) update_gpage.update_garden( update_frequency=derive_duration_string(update_frequency)) self.wait_for_page_to_be_loaded(update_gpage) update_gpage.garden_detail_nav_button.click() self.wait_for_page_to_be_loaded(detail_gpage) # the user also changes the configs for a watering station selected_watering_station = 1 detail_gpage.watering_station = selected_watering_station detail_ws_page = WateringStationDetailPage(self.driver) detail_ws_page.edit_button.click() update_ws_page = WateringStationUpdatePage(self.driver) self.wait_for_page_to_be_loaded(update_ws_page) ws_status = not list(self.garden.watering_stations.all() )[selected_watering_station].status moisture_threshold = 80 watering_duration = timedelta(minutes=3, seconds=2) update_ws_page.status = ws_status update_ws_page.moisture_threshold = moisture_threshold update_ws_page.watering_duration = derive_duration_string( watering_duration) update_ws_page.submit_button.click() # when the MC sends another GET request to the watering station api, it recieves the new updated configs # self.send_get_request_to_watering_station_api(self.garden) resp = self.api_client.get( self.get_watering_station_api_url(self.garden)) assert resp.status_code == status.HTTP_200_OK for i, ws_config in enumerate(resp.data, start=1): if i == selected_watering_station: assert ws_config['status'] == ws_status assert ws_config['moisture_threshold'] == moisture_threshold assert ws_config[ 'watering_duration'] == watering_duration.total_seconds() else: assert ws_config['status'] == _default_status() assert ws_config[ 'moisture_threshold'] == _default_moisture_threshold() assert ws_config[ 'watering_duration'] == _default_watering_duration( ).total_seconds() # similarly when it sends a GET request to the garden api, it recieves the new updated configs resp = self.api_client.get(self.get_garden_api_url(self.garden)) assert resp.status_code == status.HTTP_200_OK assert resp.data['update_frequency'] == update_frequency.total_seconds( ) # the user then goes to the update garden page update_ws_page.garden_detail_nav_button.click() self.wait_for_page_to_be_loaded(detail_gpage) detail_gpage.edit_button.click() self.wait_for_page_to_be_loaded(update_gpage) # they then click the reset api key button and the api key resets update_gpage.reset_api_key_button.click() assert wait_for(lambda: '*' not in str(update_gpage.api_key)) assert update_gpage.api_key != self.token_uuid # the microcontroller then tries to access the api, but the operation is not allowed resp = self.api_client.get( self.get_watering_station_api_url(self.garden)) assert resp.status_code == status.HTTP_403_FORBIDDEN resp = self.api_client.get(self.get_garden_api_url(self.garden)) assert resp.status_code == status.HTTP_403_FORBIDDEN resp = self.api_client.post( self.get_watering_station_api_url(self.garden)) assert resp.status_code == status.HTTP_403_FORBIDDEN resp = self.api_client.patch(self.get_garden_api_url(self.garden)) assert resp.status_code == status.HTTP_403_FORBIDDEN