Esempio n. 1
0
    def process(self, url):
        """Downloads all requested files from specified server"""
        self.ignored = 0
        self.not_selected = 0
        self.processed = 0

        api = API(url, self.user, self.password)

        if self.recording_id:
            recording = api.get(self.recording_id)
            self._download(recording, api, Path(self.out_folder))
            return
        print("Querying server {0}".format(url))
        print("Limit is {0}".format(self.limit))
        print("Tag mode {0}".format(self.tag_mode))
        print("Dates are {0} - {1}".format(self.start_date, self.end_date))
        print("Required tags are {0}".format(self.only_tags))
        print("Ignore tags are {0}".format(self.ignore_tags))
        pool = Pool(self.workers, self._downloader, api, Path(self.out_folder))
        offset = 0
        if len(self.only_tags) == 0:
            self.only_tags = None
        remaining = self.limit
        while self.limit is None or offset < self.limit:
            rows = api.query(
                limit=remaining,
                startDate=self.start_date,
                endDate=self.end_date,
                tagmode=self.tag_mode,
                tags=self.only_tags,
                offset=offset,
            )
            if len(rows) == 0:
                break
            offset += len(rows)
            if remaining:
                remaining -= len(rows)

            if self.auto_delete:
                self.update_file_locations()

            for row in rows:
                pool.put(row)
        pool.stop()
Esempio n. 2
0
def main():
    args = parse_args()
    api = UserAPI(args.server, args.user, args.password)
    out_dir = Path(args.out_folder)
    tests = []
    test_data = TestConfig(recording_tests=tests,
                           server=args.server,
                           clip_dir=args.out_folder)
    for rec_id in args.ids:
        rec_meta = api.get(rec_id)
        tracks = api.get_tracks(rec_id)
        filename = Path(rec_id).with_suffix(".cptv")
        fullpath = out_dir / filename
        tests.append(TestRecording.load_from_meta(rec_meta, tracks, filename))
        if not fullpath.exists():
            if iter_to_file(fullpath, api.download_raw(rec_id)):
                print("Saved {} - {}".format(rec_id, fullpath))
            else:
                print("error saving {}".format(rec_id))
    if os.path.exists(args.test_file):
        os.remove(args.test_file)
    with open(args.test_file, "w") as f:
        yaml.dump(test_data, f)
Esempio n. 3
0
class Mocked_cacophonyapi(unittest.TestCase):
    """Test client calls to the mocked CacophonyServer object."""
    def setUp(self):
        """Initialize an instance of mocked CacophonyServer object."""
        # By default, raise exceptions on warnings
        warnings.simplefilter("error", FutureWarning)
        with requests_mock.Mocker() as m:
            m.register_uri(
                requests_mock.POST,
                "{apiURL}/authenticate_user".format(apiURL=defaults["apiURL"]),
                status_code=204,
            )
            self.cli = UserAPI(
                baseurl=defaults["apiURL"],
                username=defaults["defaultUsername"],
                password=defaults["defaultuserPassword"],
            )
            # print("SETUP: last request.body:{}".format(m.last_request.body))

    def test_scheme(self):
        """Set up the test schema for mocked CacophonyServer object."""
        with requests_mock.Mocker() as m:
            m.register_uri(
                requests_mock.POST,
                "{apiURL}/authenticate_user".format(apiURL=defaults["apiURL"]),
                status_code=204,
            )
            cli = UserAPI(
                baseurl=defaults["apiURL"],
                username=defaults["defaultUsername"],
                password=defaults["defaultuserPassword"],
            )
            # print(m.last_request.body)
            self.assertEqual(defaults["apiURL"], cli._baseurl)

    def test_version(self):
        """Test UserAPI.version"""
        with requests_mock.Mocker() as m:
            m.register_uri(
                requests_mock.POST,
                "{apiURL}/authenticate_user".format(apiURL=defaults["apiURL"]),
                status_code=204,
            )
            cli = UserAPI(
                baseurl=defaults["apiURL"],
                username=defaults["defaultUsername"],
                password=defaults["defaultuserPassword"],
            )
            # print(m.last_request.body)
            self.assertEqual(defaults["apiURL"], cli._baseurl)

            versionResult = cli.version
            print("version:{}".format(versionResult))
            self.assertTrue(isinstance(versionResult, str))
            self.assertTrue(len(versionResult) > 0)

    def test_query(self):
        """Test UserAPI.query from mocked CacophonyServer object."""
        testcases = [
            {
                "apiPATH": "/api/v1/recordings",
                "parameters": {
                    "endDate": _strToSqlDateTime("2019-11-06 06:30:00"),
                    "startDate": _strToSqlDateTime("2019-11-01 19:00:00"),
                    "limit": 300,
                    "offset": 0,
                    "tagmode": "any",
                },
                "expectedRequestQS": {
                    "where": [
                        '{"recordingdatetime": {"$gte": "2019-11-01t19:00:00", "$lte": "2019-11-06t06:30:00"}}'
                    ],
                    "limit": ["300"],
                    "offset": ["0"],
                    "tagmode": ["any"],
                },
                "expectedResult": {
                    "outcome": "success",
                    "validator": lambda test: test,
                },
                "mockRequestJsonResponse": {
                    "rows": {
                        "key1": "value1",
                        "key2": "value2"
                    }
                },
                "mockRequestStatusCode": 200,
            },
            # TODO: check expectedRequestQS 'tags'
            {
                "Description": """
              All query paramters excluding raw_json
              """,
                "apiPATH": "/api/v1/recordings",
                "parameters": {
                    "endDate": _strToSqlDateTime("2019-11-06 06:30:00"),
                    "startDate": _strToSqlDateTime("2019-11-01 19:00:00"),
                    "devices": ["test_device1", "test_device2"],
                    "type_": "thermalRaw",
                    "min_secs": 15,  # duration
                    "limit": 300,
                    "offset": 0,
                    "tagmode": "any",
                    "tags": json.dumps('{fields:["animal"],unique:false}'),
                },
                "expectedRequestQS": {
                    "where": [
                        '{"type": "thermalraw", "duration": {"$gte": 15}, "recordingdatetime": {"$gte": "2019-11-01t19:00:00", "$lte": "2019-11-06t06:30:00"}, "deviceid": ["test_device1", "test_device2"]}'
                    ],
                    "limit": ["300"],
                    "offset": ["0"],
                    "tagmode": ["any"],
                    "tags":
                    ['"\\"{fields:[\\\\\\"animal\\\\\\"],unique:false}\\""'],
                },
                "expectedResult": {
                    "outcome": "success",
                    "validator": lambda test: test,
                },
                "mockRequestJsonResponse": {
                    "rows": {
                        "key1": "value1",
                        "key2": "value2"
                    }
                },
                "mockRequestStatusCode": 200,
            },
            # TODO: check expectedRequestQS 'tags'
            {
                "Description": """
              All query paramters including raw_json=True
              """,
                "apiPATH": "/api/v1/recordings",
                "parameters": {
                    "endDate": _strToSqlDateTime("2019-11-06 06:30:00"),
                    "startDate": _strToSqlDateTime("2019-11-01 19:00:00"),
                    "devices": ["test_device1", "test_device2"],
                    "type_": "thermalRaw",
                    "min_secs": 15,  # duration
                    "limit": 300,
                    "offset": 0,
                    "tagmode": "any",
                    "tags": json.dumps('{fields:["animal"],unique:false}'),
                    "raw_json": True,
                },
                "expectedRequestQS": {
                    "where": [
                        '{"type": "thermalraw", "duration": {"$gte": 15}, "recordingdatetime": {"$gte": "2019-11-01t19:00:00", "$lte": "2019-11-06t06:30:00"}, "deviceid": ["test_device1", "test_device2"]}'
                    ],
                    "limit": ["300"],
                    "offset": ["0"],
                    "tagmode": ["any"],
                    "tags":
                    ['"\\"{fields:[\\\\\\"animal\\\\\\"],unique:false}\\""'],
                },
                "expectedResult": {
                    "outcome": "successRawData",
                    "validator": lambda test: test,
                },
                "mockRequestJsonResponse": {
                    "key1": "value1",
                    "key2": "value2"
                },
                "mockRequestStatusCode": 200,
            },
        ]

        for tc in testcases:
            print(tc)
            with requests_mock.Mocker() as m:
                m.register_uri(
                    requests_mock.GET,
                    "{apiURL}{apiPATH}".format(apiURL=defaults["apiURL"],
                                               apiPATH=tc["apiPATH"]),
                    json=tc["mockRequestJsonResponse"],
                    status_code=200,
                )
                _ = self.cli.query(**tc["parameters"])

                # _ = self.cli.query(
                #         endDate=_strToSqlDateTime("2019-11-06 06:30:00"),
                #         startDate=_strToSqlDateTime("2019-11-01 19:00:00"),
                #         limit=300,
                #         offset=0,
                #         tagmode="any")

                print(m.last_request.qs)

                self.assertEqual(m.last_request.qs, tc["expectedRequestQS"])

    def test_get_valid_recordingId(self):
        """Test UserAPI.get with a valid recording_id from mocked CacophonyServer object."""
        # TODO: handle error as correct result
        int_recording_id = 432109
        str_recording_id = "432109"
        mock_json_result = {"key1": "value1", "key2": "value2"}
        with requests_mock.Mocker() as m:
            m.register_uri(
                requests_mock.GET,
                "{apiURL}/api/v1/recordings/{recording_id}".format(
                    apiURL=defaults["apiURL"],
                    recording_id="{}".format(int_recording_id),
                ),
                json={"recording": mock_json_result},
                status_code=200,
            )
            result = self.cli.get(int_recording_id)

            # print(m.last_request.qs)
            self.assertEqual(result, mock_json_result)
            self.assertEqual(m.last_request.qs, {})
            self.assertEqual(
                m.last_request.path,
                "/api/v1/recordings/{str_recording_id}".format(
                    str_recording_id=str_recording_id),
            )

    def test_get_invalid_recordingId(self):
        """Test UserAPI.get with an invalid recording_id from mocked CacophonyServer object."""
        int_recording_id = ""
        str_recording_id = ""
        mock_json_result = "some sort of error message"
        # result=None
        # TODO: What about exceptions 301, 100 etc

        for status_code in [400, 422, 500, 501]:
            with self.assertRaises(Exception) as context:
                # Setup expected request
                with requests_mock.Mocker() as m:
                    m.register_uri(
                        requests_mock.GET,
                        "{apiURL}/api/v1/recordings/{recording_id}".format(
                            apiURL=defaults["apiURL"],
                            recording_id="{}".format(int_recording_id),
                        ),
                        json={"message": mock_json_result},
                        status_code=status_code,
                    )
                    # TEST
                    _ = self.cli.get(int_recording_id)

            if status_code in [400, 422]:
                self.assertTrue(type(context.exception) == OSError)
                self.assertTrue(
                    "request failed ({status_code}): {message}".format(
                        status_code=status_code, message=mock_json_result) in
                    str(context.exception))
            elif status_code in [500, 501]:
                # TODO: look at improved assertions
                # print("{}".format(context.exception))
                self.assertTrue(
                    type(context.exception) == requests.exceptions.HTTPError)
                self.assertTrue("{status_code} Server Error: None".format(
                    status_code=status_code) in "{}".format(context.exception))
            else:
                self.assertFalse(True)

            # TODO: check what exception was raised
            # print("This exception raised:{}".format(context.exception))

    def test_valid_get_tracks(self):
        """Test UserAPI.get_tracks with a valid recording_id from mocked CacophonyServer object."""
        # TODO: handle error as correct result
        int_recording_id = 432109
        str_recording_id = "432109"
        mock_json_result = {"key1": "value1", "key2": "value2"}
        with requests_mock.Mocker() as m:
            m.register_uri(
                requests_mock.GET,
                "{apiURL}/api/v1/recordings/{recording_id}/tracks".format(
                    apiURL=defaults["apiURL"],
                    recording_id="{}".format(int_recording_id),
                ),
                json=mock_json_result,
                status_code=200,
            )
            result = self.cli.get_tracks(int_recording_id)

            # print(m.last_request.qs)
            self.assertEqual(result, mock_json_result)
            self.assertEqual(m.last_request.qs, {})
            self.assertEqual(
                m.last_request.path,
                "/api/v1/recordings/{str_recording_id}/tracks".format(
                    str_recording_id=str_recording_id),
            )

    def test_valid_get_groups(self):
        """Test UserAPI.get_groups (no parameters passed) from mocked CacophonyServer object."""
        # TODO: handle error as correct result
        mock_json_result = {"key1": "value1", "key2": "value2"}
        mock_qs = {"where": ["{}"]}
        with requests_mock.Mocker() as m:
            m.register_uri(
                requests_mock.GET,
                "{apiURL}/api/v1/groups".format(apiURL=defaults["apiURL"]),
                json=mock_json_result,
                status_code=200,
            )
            result = self.cli.get_groups_as_json()

            # print(m.last_request.qs)
            self.assertEqual(result, mock_json_result)
            self.assertEqual(m.last_request.qs, mock_qs)
            self.assertEqual(m.last_request.path, "/api/v1/groups")

    def test_valid_get_devices(self):
        """Test UserAPI.get_devices (no parameters passed) from mocked CacophonyServer object."""

        mock_apiPath = "/api/v1/devices"
        mock_json_result = {
            "devices": {
                "rows": {
                    "key1": "value1",
                    "key2": "value2"
                }
            }
        }
        mock_qs = {"where": ["{}"]}
        with requests_mock.Mocker() as m:
            m.register_uri(
                requests_mock.GET,
                "{apiURL}{apiPath}".format(apiURL=defaults["apiURL"],
                                           apiPath=mock_apiPath),
                json=mock_json_result,
                status_code=200,
            )
            result = self.cli.get_devices_as_json()

            # print(m.last_request.qs)
            self.assertEqual(result, mock_json_result["devices"]["rows"])
            self.assertEqual(m.last_request.qs, mock_qs)
            self.assertEqual(m.last_request.path, mock_apiPath)

    def test_valid_list_files(self):
        """Test UserAPI.list_files (no parameters passed) from mocked CacophonyServer object."""

        mock_apiPath = "/api/v1/files"
        mock_json_result = {"rows": {"key1": "value1", "key2": "value2"}}
        mock_qs = {"where": ["{}"], "order": ['["id"]']}
        with requests_mock.Mocker() as m:
            m.register_uri(
                requests_mock.GET,
                "{apiURL}{apiPath}".format(apiURL=defaults["apiURL"],
                                           apiPath=mock_apiPath),
                json=mock_json_result,
                status_code=200,
            )
            result = self.cli.list_files()

            # print(m.last_request.qs)
            self.assertEqual(result, mock_json_result["rows"])
            self.assertEqual(m.last_request.qs, mock_qs)
            self.assertEqual(m.last_request.path, mock_apiPath)

    def test_valid_delete_files(self):
        """Test UserAPI.delete_file (file_id passed) from mocked CacophonyServer object."""
        mock_int_file_id = 123456
        mock_str_file_id = "123456"
        mock_apiPath = "/api/v1/files/{file_id}".format(
            file_id=mock_int_file_id)
        mock_json_result = {"rows": {"key1": "value1", "key2": "value2"}}
        mock_qs = {}
        with requests_mock.Mocker() as m:
            m.register_uri(
                requests_mock.DELETE,
                "{apiURL}{apiPath}".format(apiURL=defaults["apiURL"],
                                           apiPath=mock_apiPath),
                json=mock_json_result,
                status_code=200,
            )
            result = self.cli.delete_file(mock_int_file_id)

            # print(m.last_request.qs)
            self.assertEqual(result, mock_json_result)
            self.assertEqual(m.last_request.qs, mock_qs)
            self.assertEqual(m.last_request.path, mock_apiPath)

    def test_valid_reprocess(self):
        """Test UserAPI.get_devices (no parameters passed) from mocked CacophonyServer object."""
        # TODO: Construct the data to pass in the post
        mock_apiPath = "/api/v1/reprocess"
        mock_recordings = [1, 2, 3]
        mock_postData = {"recordings": mock_recordings}
        # mock_json_result = {"devices":{"rows":{'key1': 'value1', 'key2': 'value2'}}}
        mock_qs = {}
        with requests_mock.Mocker() as m:
            m.register_uri(
                requests_mock.POST,
                "{apiURL}{apiPath}".format(apiURL=defaults["apiURL"],
                                           apiPath=mock_apiPath),
                json=mock_postData,
                status_code=200,
            )
            result = self.cli.reprocess(mock_recordings)

            # print(m.last_request.qs)
            self.assertEqual(result, mock_postData)
            self.assertEqual(m.last_request.qs, mock_qs)
            self.assertEqual(m.last_request.path, mock_apiPath)

    def test_valid_upload_recording(self):
        """Test UserAPI.upload_recordings ( parameters passed: groupname, devicename, filename, props) to mocked CacophonyServer object.


        # Range of expectedResult

        'expectedResult':{'outcome':'success',
                                'validator':lambda test: test},
        'expectedResult':{'outcome':'failurePreRequest',
                                'validator':lambda test: test},
        'expectedResult':{'outcome':'failureOnRequest',
                                'validator':lambda test: test},



        """
        # TODO: Construct the data to pass in the post
        mock_apiPath = lambda devicename, groupname: "/api/v1/recordings/device/{devicename}/group/{groupname}".format(
            devicename=devicename, groupname=groupname)

        # TODO: construct the success validatior
        # TODO: construct the failure validtor
        testcases = [
            {
                "filename": "test1.cptv",
                "mock_file.side_effect": None,
                "devicename": "deviceTEST1234",
                "groupname": "groupTEST1234",
                "prop": None,
                "expectedProp": {
                    "type": "thermalRaw"
                },
                "expectedResult": {
                    "outcome": "success",
                    "validator": lambda test: test,
                },
                "mockRequestJsonResponse": {
                    "success": True
                },
                "mockRequestStatusCode": 200,
            },
            # TODO: adjust test mock/asserts to handle IOError
            # {'filename':"test1.cptv",
            #   'mock_file.side_effect':IOError(),
            #   'devicename':'deviceTEST1234',
            #   'groupname':'groupTEST1234',
            #   'prop':None,
            #   'expectedProp':{'type':'thermalRaw'},
            #   'mockRequestStatusCode':200
            #  },
            {
                "filename": "test2.mp3",
                "mock_file.side_effect": None,
                "devicename": "deviceTEST1234",
                "groupname": "groupTEST1234",
                "prop": None,
                "expectedProp": {
                    "type": "audio"
                },
                "expectedResult": {
                    "outcome": "success",
                    "validator": lambda test: test,
                },
                "mockRequestJsonResponse": {
                    "success": True
                },
                "mockRequestStatusCode": 200,
            },
            # TODO: what assert/mock to do for unknown file type
            {
                "filename": "test3.xyz",
                "mock_file.side_effect": None,
                "devicename": "deviceTEST1234",
                "groupname": "groupTEST1234",
                "prop": None,
                "expectedProp": None,
                "expectedResult": {
                    "outcome": "failurePreRequest",
                    "validator": lambda test: test,
                },
                "mockRequestJsonResponse": None,
                "mockRequestStatusCode": None,
            },
        ]

        for tc in testcases:
            # TODO: pretty print
            print(tc)
            """
                This Test is tough to design/debug
                made easier by using mock-open module https://github.com/nivbend/mock-open

                mock the file to upload
                Last, you also need to mock os.fstat() so that permissions are correct
                (Clues in https://gist.github.com/dmerejkowsky/d11e3c68be6a96387dea3d8b6a409b40#file-test_netrc-py-L17)
            """

            mock_open = MockOpen(read_data="0123456789012345678901234")
            # mock_open = MockOpen(read_data='',side_effect=IOError('no file'))

            mock_open[os.path.join(
                os.path.dirname(__file__),
                tc["filename"])].read_data = "0123456789012345678901234"
            mock_open[os.path.join(
                os.path.dirname(__file__),
                tc["filename"])].side_effect = tc["mock_file.side_effect"]
            fake_stat = mock.MagicMock("fstat")
            # fake_stat.st_uid = # os.getuid()
            fake_stat.st_mode = 0o600
            fake_stat.st_size = 25

            with patch("builtins.open",
                       mock_open), patch("os.fstat") as mock_fstat:
                mock_fstat.return_value = fake_stat

                with requests_mock.Mocker() as m:
                    m.register_uri(
                        requests_mock.POST,
                        "{apiURL}{apiPath}".format(
                            apiURL=defaults["apiURL"],
                            apiPath=mock_apiPath(tc["devicename"],
                                                 tc["groupname"]),
                        ),
                        json=tc["mockRequestJsonResponse"],
                        status_code=tc["mockRequestStatusCode"],
                    )
                    if tc["expectedResult"]["outcome"] == "success":
                        # ----------------------- API call UNDERTEST ------------------
                        result = self.cli.upload_recording(
                            tc["groupname"],
                            tc["devicename"],
                            tc["filename"],
                            tc["prop"],
                        )
                        # ----------------------------------------------------------------
                        print(result)
                        # TODO: FILL in the TEST assertions in place of the following
                        self.assertEqual(result, tc["mockRequestJsonResponse"])
                        self.assertTrue(
                            tc["expectedResult"]["validator"](True))

                    elif tc["expectedResult"][
                            "outcome"] == "failurePreRequest":
                        with self.assertRaises(ValueError):
                            # ----------------------- API call UNDERTEST ------------------
                            result = self.cli.upload_recording(
                                tc["groupname"],
                                tc["devicename"],
                                tc["filename"],
                                tc["prop"],
                            )
                    # ----------------------------------------------------------------
                    else:
                        # TODO: Check the mockrequest was called, and mockfile
                        self.assertTrue(
                            False,
                            "Something not being checked ----------------------------",
                        )
                    # mock_file.assert_called_with(tc['filename'], 'rb')
                    # self.assertEqual(m.last_request.path, mock_apiPath)

    def test_valid_download(self):
        """Test UserAPI.download with a valid recording_id from mocked CacophonyServer object."""

        int_recording_id = 432109
        str_recording_id = "432109"

        mock_apiPathTokenRequest = "/api/v1/recordings/{recording_id}".format(
            recording_id="{}".format(int_recording_id))
        mock_apiPathTokenProcess = "/api/v1/signedUrl"

        mock_json_result = {"key1": "value1", "key2": "value2"}

        token = "asfasdfdasfdasfadsf"

        # First Test getting the download token
        with requests_mock.Mocker() as m:
            m.register_uri(
                requests_mock.GET,
                "{apiURL}{apiPath}".format(apiURL=defaults["apiURL"],
                                           apiPath=mock_apiPathTokenRequest),
                json={"downloadFileJWT": token},
                status_code=200,
            )
            recordingStreamGenerator = self.cli.download(int_recording_id)

            self.assertEqual(type(recordingStreamGenerator),
                             types.GeneratorType)
            self.assertEqual(m.last_request.qs, {})
            self.assertEqual(
                m.last_request.path,
                "/api/v1/recordings/{str_recording_id}".format(
                    str_recording_id=str_recording_id),
            )

        # Now test the stream retrieval
        # TODO: Is it necessary to consider smaller or bigger stream byte retures
        expectedReturn1 = bytes([random.randint(1, 254) for _ in range(4096)])
        with requests_mock.Mocker() as m:
            m.register_uri(
                requests_mock.GET,
                "{apiURL}{apiPath}".format(apiURL=defaults["apiURL"],
                                           apiPath=mock_apiPathTokenProcess),
                # params={"jwt":token},
                content=expectedReturn1,
                status_code=200,
            )
            buffer = next(recordingStreamGenerator)
        self.assertEqual(buffer, expectedReturn1)

    def test_valid__all_downloads(self):
        """Test UserAPI.download and Test UserAPI.download_raw with a valid recording_id from mocked CacophonyServer object."""

        for test in [
            {
                "artifactID":
                123490,
                "apipath":
                lambda int_recording_id: "/api/v1/recordings/{recording_id}".
                format(recording_id=int_recording_id),
                "UserAPIcall":
                self.cli.download,
                "UserAPIcallJSONresponse": {
                    "downloadFileJWT": "abcdTOKENdfg"
                },
            },
            {
                "artifactID":
                123490,
                "apipath":
                lambda int_recording_id: "/api/v1/recordings/{recording_id}".
                format(recording_id=int_recording_id),
                "UserAPIcall":
                self.cli.download_raw,
                "UserAPIcallJSONresponse": {
                    "downloadRawJWT": "abcdTOKENdfg"
                },
            },
            {
                "artifactID":
                123490,
                "apipath":
                lambda int_file_id: "/api/v1/files/{file_id}".format(
                    file_id=int_file_id),
                "UserAPIcall":
                self.cli.download_file,
                "UserAPIcallJSONresponse": {
                    "file": "somedata",
                    "jwt": "abcdTOKENdfg"
                },
            },
        ]:
            self._generic_download_test(downloadtype=test)

    def _generic_download_test(self, downloadtype):
        print(downloadtype)

        mock_apiPathTokenRequest = downloadtype["apipath"](
            downloadtype["artifactID"])
        mock_apiPathTokenProcess = "/api/v1/signedUrl"

        token = "asfasdfdasfdasfadsf"
        # First Test getting the download token
        with requests_mock.Mocker() as m:
            m.register_uri(
                requests_mock.GET,
                "{apiURL}{apiPath}".format(
                    apiURL=defaults["apiURL"],
                    apiPath=downloadtype["apipath"](
                        downloadtype["artifactID"]),
                ),
                json=downloadtype["UserAPIcallJSONresponse"],
                status_code=200,
            )

            result = downloadtype["UserAPIcall"](downloadtype["artifactID"])
            # TODO: handle dowloadfile returns a tuple)
            if isinstance((result), tuple):
                streamGenerator = result[1]
                self.assertIsNotNone(result[0])
            else:
                streamGenerator = result

            self.assertEqual(type(streamGenerator), types.GeneratorType)
            self.assertEqual(m.last_request.qs, {})
            self.assertEqual(
                m.last_request.path,
                downloadtype["apipath"](downloadtype["artifactID"]))

        # Now test the stream retrieval
        # TODO: Is it necessary to consider smaller or bigger stream byte retures
        expectedReturn1 = bytes([random.randint(1, 254) for _ in range(4096)])
        with requests_mock.Mocker() as m:
            m.register_uri(
                requests_mock.GET,
                "{apiURL}{apiPath}".format(apiURL=defaults["apiURL"],
                                           apiPath=mock_apiPathTokenProcess),
                content=expectedReturn1,
                status_code=200,
            )
            buffer = next(streamGenerator)
        self.assertEqual(buffer, expectedReturn1)
def main():
    parser = argparse.ArgumentParser()

    parser.add_argument("out_folder",
                        help="Folder to place downloaded files in")
    parser.add_argument("user", help="API server username")
    parser.add_argument("password", help="API server password")
    parser.add_argument(
        "-s",
        "--server",
        default="https://api.cacophony.org.nz",
        help="Cacophony API Server URL",
    )
    parser.add_argument(
        "--start-date",
        type=parsedate,
        help=
        "If specified, only files recorded on or after this date will be downloaded.",
    )
    parser.add_argument(
        "--end-date",
        type=parsedate,
        help=
        "If specified, only files recorded before or on this date will be downloaded.",
    )
    # FIXME - this should take device names
    parser.add_argument(
        "-d",
        "--device",
        type=int,
        action="append",
        help="Limit to this integer device id (can be used multiple times)",
    )
    parser.add_argument(
        "-w",
        "--workers",
        type=int,
        default=4,
        help="Number of concurrent downloads to run",
    )
    parser.add_argument(
        "-L",
        "--local-time",
        default=False,
        action="store_true",
        help="Convert timestamps in filenames to local time",
    )
    parser.add_argument(
        "-i",
        "--id",
        type=int,
        default=None,
        help="Download a specific recording (other options are ignored)",
    )

    args = parser.parse_args()

    args.out_folder = Path(args.out_folder)
    args.out_folder.mkdir(exist_ok=True)

    print("Querying recordings")
    api = API(args.server, args.user, args.password)
    if args.id is not None:
        recordings = [api.get(args.id)]
    else:
        recordings = api.query(
            type_="audio",
            startDate=args.start_date,
            endDate=args.end_date,
            devices=args.device,
            limit=99999,
        )
    print("Found {} recordings".format(len(recordings)))

    pool = Pool(args.workers, download, api, args)
    for recording in recordings:
        pool.put(recording)
    pool.stop()