Example #1
0
    def test_sync_pull_deleted_obs(self):
        """
        We have an observation in our database with a iNaturalist ID, but when we pull from the iNaturalist API
        this observation is not returned.
        So we check the observation individually and in this case we conclude it no longer exist.
        In this scenario, the observation should be deleted locally
        """
        # Create an Individual that already exists in iNaturalist after a previous push
        ind = Individual(inaturalist_id=30,
                         latitude=51.2003,
                         longitude=4.9067,
                         observation_time=datetime(2019, 4, 1, 10),
                         originates_in_vespawatch=True,
                         taxon=self.vv_taxon)
        ind.save()

        # Set a return value for the self.get_all_mock. It should return no observations
        self.get_all_mock.return_value = []

        # Set a side effect for the self.get_obs_mock. It should raise a ObservationNotFound exception
        self.get_obs_mock.side_effect = ObservationNotFound()

        # Run inaturalist sync.
        call_command('inaturalist_sync')
        # Assert that the individual is deleted
        self.assertEqual(len(Individual.objects.all()), 0)
Example #2
0
    def test_sync_pull_obs_taxon_changed_unknown(self):
        """
        We have an observation in our database with a iNaturalist ID, but when we pull from the iNaturalist API
        this observation is not returned.
        So we check the observation individually and in this case we conclude the taxon is not known in our database
        It doesn't matter where this observation comes from, we should flag it as "unknown taxon"
        """
        # Create an Individual that already exists in iNaturalist after a previous push
        ind = Individual(inaturalist_id=30,
                         latitude=51.2003,
                         longitude=4.9067,
                         observation_time=datetime(2019, 4, 1, 10),
                         originates_in_vespawatch=True,
                         taxon=self.vv_taxon)
        ind.save()

        # Set a return value for the self.get_all_mock. It should return no observations
        self.get_all_mock.return_value = []

        # Set a side effect for the self.get_obs_mock. It should return an observation that has no vespawatch project id
        self.get_obs_mock.return_value = {
            'id': 30,
            'taxon': {
                'id': 2732,
                'name': 'Unknown taxon'
            },
            'description': '',
            'geojson': {
                'coordinates': [10, 20]
            },
            'observed_on_string': '2019-04-01T11:20:00+00:00',
            'observed_time_zone': 'Europe/Brussels',
            'photos': [],
            'project_ids': [11]  # some random project
        }

        # Assert that the individual had no warning before the sync
        self.assertEqual(
            len(
                Individual.objects.filter(
                    inaturalist_id=30)[0].warnings.all()), 0)
        # Run inaturalist sync.
        call_command('inaturalist_sync')
        # Assert that the individual has a warning now
        self.assertEqual(
            len(
                Individual.objects.filter(
                    inaturalist_id=30)[0].warnings.all()), 2)
        warning_texts = sorted([
            x.text for x in Individual.objects.filter(
                inaturalist_id=30)[0].warnings.all()
        ])
        self.assertEqual(warning_texts[0], 'not in vespawatch project')
        self.assertEqual(warning_texts[1], 'unknown taxon')
        # Assert that the inaturalist_species field was set to the taxon name
        self.assertEqual(
            Individual.objects.filter(
                inaturalist_id=30)[0].inaturalist_species, 'Unknown taxon')
        self.assertEqual(
            Individual.objects.filter(inaturalist_id=30)[0].taxon, None)
Example #3
0
    def test_sync_pull_obs_taxon_changed_known(self):
        """
        We have an observation in our database with a iNaturalist ID, but when we pull from the iNaturalist API
        this observation is not returned.
        So we check the observation individually and in this case we conclude the taxon is not Vespa velutina (so
        the project id is not the one we expect) but it is another taxon that is known in our database.
        In this case, we should flag it as `not in vespawatch project` but we should not flag it as
        `taxon unkown`
        """
        # Create an Individual that already exists in iNaturalist after a previous push
        ind = Individual(inaturalist_id=30,
                         latitude=51.2003,
                         longitude=4.9067,
                         observation_time=datetime(2019, 4, 1, 10),
                         originates_in_vespawatch=True,
                         taxon=self.vv_taxon)
        ind.save()

        # Set a return value for the self.get_all_mock. It should return no observations
        self.get_all_mock.return_value = []

        # Set a side effect for the self.get_obs_mock. It should return an observation that has no vespawatch project id
        self.get_obs_mock.return_value = {
            'id': 30,
            'taxon': {
                'id': self.other_taxon.inaturalist_pull_taxon_ids[0]
            },
            'description': '',
            'geojson': {
                'coordinates': [10, 20]
            },
            'observed_on_string': '2019-04-01T11:20:00+00:00',
            'observed_time_zone': 'Europe/Brussels',
            'photos': [],
            'project_ids': [11]  # some random project
        }

        # Assert that the individual had no warning before the sync
        self.assertEqual(
            len(
                Individual.objects.filter(
                    inaturalist_id=30)[0].warnings.all()), 0)
        # Run inaturalist sync.
        call_command('inaturalist_sync')
        # Assert that the individual has a warning now
        self.assertEqual(
            len(
                Individual.objects.filter(
                    inaturalist_id=30)[0].warnings.all()), 1)
        self.assertEqual(
            Individual.objects.filter(
                inaturalist_id=30)[0].warnings.all()[0].text,
            'not in vespawatch project')
        # Make sure the taxon is also updated
        self.assertEqual(
            Individual.objects.filter(inaturalist_id=30)[0].taxon.id,
            self.other_taxon.id)
Example #4
0
    def test_sync_pull_obs_out_project(self):
        """
        We have an observation in our database with a iNaturalist ID, but when we pull from the iNaturalist API
        this observation is not returned.
        So we check the observation individually and in this case we conclude it is no longer part of the Vespawatch project.
        It doesn't matter where this observation comes from, we should flag it as "not in vespawatch project"
        """
        # Create an Individual that already exists in iNaturalist after a previous push
        ind = Individual(inaturalist_id=30,
                         latitude=51.2003,
                         longitude=4.9067,
                         observation_time=datetime(2019, 4, 1, 10),
                         originates_in_vespawatch=True,
                         taxon=self.vv_taxon)
        ind.save()

        # Set a return value for the self.get_all_mock. It should return no observations
        self.get_all_mock.return_value = []

        # Set a side effect for the self.get_obs_mock. It should return an observation that has no vespawatch project id
        self.get_obs_mock.return_value = {
            'id': 30,
            'taxon': {
                'id': self.vv_taxon.inaturalist_pull_taxon_ids[0]
            },
            'description': '',
            'geojson': {
                'coordinates': [10, 20]
            },
            'observed_on_string': '2019-04-01T11:20:00+00:00',
            'observed_time_zone': 'Europe/Brussels',
            'photos': [],
            'project_ids': [999]  # not vespawatch
        }

        # Assert that the individual had no warning before the sync
        self.assertEqual(
            len(
                Individual.objects.filter(
                    inaturalist_id=30)[0].warnings.all()), 0)
        self.assertEqual(len(Taxon.objects.all()), 2)

        # Run inaturalist sync.
        call_command('inaturalist_sync')
        # Assert that the individual has a warning now
        self.assertEqual(
            len(
                Individual.objects.filter(
                    inaturalist_id=30)[0].warnings.all()), 1)
        self.assertEqual(
            Individual.objects.filter(
                inaturalist_id=30)[0].warnings.all()[0].text,
            'not in vespawatch project')
Example #5
0
    def test_sync_pull_obs_vw_evidence_changed_to_nest_orig_inat(self):
        """
        An individual was flagged as a nest on iNaturalist
          -> the observation originated in inaturalist => delete the individual and create a nest

        """
        # Create an Individual that already exists in iNaturalist after a previous push
        ind = Individual(inaturalist_id=30,
                         latitude=51.2003,
                         longitude=4.9067,
                         observation_time=datetime(2019, 4, 1, 10),
                         originates_in_vespawatch=False,
                         taxon=self.vv_taxon)
        ind.save()

        self.get_all_mock.return_value = [{
            'id':
            30,
            'description':
            '',
            'geojson': {
                'coordinates': [10, 20]
            },
            'ofvs': [{
                'field_id': settings.VESPAWATCH_EVIDENCE_OBS_FIELD_ID,
                'value': 'nest'
            }],
            'observed_on_string':
            '2019-04-01T11:20:00+00:00',
            'observed_time_zone':
            'Europe/Brussels',
            'photos': [],
            'taxon': {
                'id': self.vv_taxon.inaturalist_pull_taxon_ids[0]
            }
        }]

        # Assert no nests exist before syncing
        self.assertEqual(len(Nest.objects.all()), 0)

        # Run inaturalist sync.
        call_command('inaturalist_sync')
        # Assert that the nest exists now
        self.assertEqual(len(Nest.objects.all()), 1)
        # Assert that the individual is deleted
        self.assertEqual(len(Individual.objects.all()), 0)
        # And that it is not added to the InatObsToDelete (otherwise we will delete it on iNaturalist at the next push)
        self.assertEqual(len(InatObsToDelete.objects.all()), 0)
Example #6
0
    def test_sync_pull_update_fields_ind(self):
        """
        When an individual exists and we get updated info from iNaturalist for that individual,
        the fields should be updated
        """
        # Create an Individual that already exists in iNaturalist after a previous push
        ind = Individual(inaturalist_id=30,
                         latitude=51.2003,
                         longitude=4.9067,
                         observation_time=datetime(2019, 4, 1, 10),
                         originates_in_vespawatch=True,
                         taxon=self.vv_taxon)
        ind.save()

        # Set a return value for the self.get_all_mock. It should return only data for the observation
        # with iNaturalist id = 30
        # Here we will assume the community_taxon_id is set to a vespawatch taxon and the coordiantes are changed
        self.get_all_mock.return_value = [{
            'id':
            30,
            'community_taxon_id':
            self.vv_taxon.inaturalist_pull_taxon_ids[0],
            'description':
            'test description',
            'geojson': {
                'coordinates': [10, 20]
            },
            'observed_on_string':
            '2019-04-01T11:20:00+00:00',
            'observed_time_zone':
            'Europe/Brussels',
            'taxon': {
                'id': self.vv_taxon.inaturalist_pull_taxon_ids[0]
            },
            'photos': []
        }]

        # Run inaturalist sync.
        call_command('inaturalist_sync')
        self.get_all_mock.assert_called_once()

        # The individual is now changed
        ind = Individual.objects.all().filter(inaturalist_id=30)[0]

        self.assertEqual(ind.longitude, 10)
        self.assertEqual(ind.latitude, 20)
        self.assertTrue(ind.inat_vv_confirmed)
        self.assertEqual(ind.comments, 'test description')
Example #7
0
    def test_sync_push_new_individual(self):
        """
        Create a test individual and run the sync command.
        Assert that the mocked iNaturalist API is called with the
        individual data + the vespawatch_evidence field set to 'individual'
        """

        # Create an Individual with minimum info
        ind = Individual(originates_in_vespawatch=True,
                         taxon=self.vv_taxon,
                         observation_time=datetime(2019, 4, 1, 10),
                         latitude=51.2003,
                         longitude=4.9067)
        ind.save()

        individual_data_to_inaturalist = {
            'taxon_id':
            ind.taxon.inaturalist_push_taxon_id,
            'observed_on_string':
            '2019-04-01T08:00:00+00:00',
            'time_zone':
            'Brussels',
            'description':
            '',
            'latitude':
            ind.latitude,
            'longitude':
            ind.longitude,
            'place_guess':
            '',
            'observation_field_values_attributes': [{
                'observation_field_id':
                settings.VESPAWATCH_ID_OBS_FIELD_ID,
                'value':
                ind.pk
            }, {
                'observation_field_id':
                settings.VESPAWATCH_EVIDENCE_OBS_FIELD_ID,
                'value':
                'individual'
            }]
        }
        call_command('inaturalist_sync', pushonly=True)
        self.create_at_inat_mock.assert_called_once()
        self.create_at_inat_mock.assert_called_with(
            access_token='TESTTOKEN',
            params={'observation': individual_data_to_inaturalist})
Example #8
0
    def test_sync_pull_obs_vw_evidence_changed_to_nest_orig_vw(self):
        """
        An individual was flagged as a nest on iNaturalist
          -> the observation originated in vespawatch => flag as "nest at inaturalist"

        """
        # Create an Individual that already exists in iNaturalist after a previous push
        ind = Individual(inaturalist_id=30,
                         latitude=51.2003,
                         longitude=4.9067,
                         observation_time=datetime(2019, 4, 1, 10),
                         originates_in_vespawatch=True,
                         taxon=self.vv_taxon)
        ind.save()

        self.get_all_mock.return_value = [{
            'id':
            30,
            'description':
            '',
            'geojson': {
                'coordinates': [10, 20]
            },
            'observed_on_string':
            '2019-04-01T11:20:00+00:00',
            'observed_time_zone':
            'Europe/Brussels',
            'ofvs': [{
                'field_id': settings.VESPAWATCH_EVIDENCE_OBS_FIELD_ID,
                'value': 'nest'
            }],
            'taxon': {
                'id': self.vv_taxon.inaturalist_pull_taxon_ids[0]
            },
            'photos': []
        }]
        # Run inaturalist sync.
        call_command('inaturalist_sync')
        # Assert that the nest has a warning now
        self.assertEqual(len(ind.warnings.all()), 1)
        self.assertEqual(ind.warnings.all()[0].text, 'nest at inaturalist')
Example #9
0
    def test_sync_no_push_deleted_nest(self):
        """
        An observation that was deleted on vespawatch before it was synced to iNaturalist should not be deleted on
        iNaturalist
        """

        # Create an Individual that already exists in iNaturalist after a previous push
        nest = Individual(latitude=51.2003,
                          longitude=4.9067,
                          observation_time=datetime(2019, 4, 1, 10),
                          originates_in_vespawatch=True,
                          taxon=self.vv_taxon)
        nest.save()
        nest.delete(
        )  # When the observation is deleted, it is deleted without creating a InatObsToDelete object

        self.assertEqual(len(InatObsToDelete.objects.all()), 0)

        # Now a sync is called. Since there are no InatObsToDelete, the delete_mock is never called
        call_command('inaturalist_sync')
        self.delete_mock.assert_not_called()
Example #10
0
    def test_sync_push_deleted_obs_doesnt_exist(self):
        """
        Pyinaturalist will raise a ObservationNotFound exception when you try to delete an observation
        that does not exist on iNaturalist.
        Make sure we catch that and properly handle it (delete the observation locally since it's
        already gone on iNaturalist)
        """
        # Create an Individual that already exists in iNaturalist after a previous push
        ind = Individual(inaturalist_id=30,
                         latitude=51.2003,
                         longitude=4.9067,
                         observation_time=datetime(2019, 4, 1, 10),
                         originates_in_vespawatch=True,
                         taxon=self.vv_taxon)
        ind.save()

        # Now delete it
        ind.delete()

        self.assertEqual(len(InatObsToDelete.objects.all()), 1)

        # Set a return value for the self.get_all_mock. It should return no observations
        self.get_all_mock.return_value = []

        # Set a side effect for the self.get_obs_mock. It should raise a ObservationNotFound exception
        self.delete_mock.side_effect = ObservationNotFound()

        # Run inaturalist sync.
        call_command('inaturalist_sync')
        self.assertEqual(len(InatObsToDelete.objects.all()), 0)


# TODO add tests for pulling and checking taxon id (first community taxon, then taxon)
# TODO check update description
# TODO check update date
# TODO check update taxon
Example #11
0
    def test_sync_push_deleted_indiv(self):
        """
        An observation that was created in vespawatch and deleted in vespawatch after it was synced, should be
        deleted on iNaturalist
        """

        # Create an Individual that already exists in iNaturalist after a previous push
        ind = Individual(inaturalist_id=30,
                         latitude=51.2003,
                         longitude=4.9067,
                         observation_time=datetime(2019, 4, 1, 10),
                         originates_in_vespawatch=True,
                         taxon=self.vv_taxon)
        ind.save()
        ind.delete(
        )  # When the observation is deleted, it is actually added to the InatObsToDelete table

        self.assertEqual(len(InatObsToDelete.objects.all()), 1)
        self.assertEqual(InatObsToDelete.objects.all()[0].inaturalist_id, 30)

        # Now a sync is called. The InatObsToDelete observation is deleted on iNaturalist and removed from the db
        call_command('inaturalist_sync')
        self.delete_mock.assert_called_once_with(observation_id=30,
                                                 access_token='TESTTOKEN')