def test_update_version_zero_empty_string(mocker): """ Test empty strings are not added to the actions """ metadata = { "general": { "id": "test_plugin", "name": "test", "qgisMinimumVersion": "0.0.0", "qgisMaximumVersion": "0.0.1", "description": "this is a test", "about": "testing", "version": "1.0.0", "author": "me", "email": "*****@*****.**", "tags": "", } } mocker.patch("pynamodb.models.Model.update") version_zero = mocker.Mock() version_zero.revisions = 0 version_zero.ended_at = None version_zero.attribute_values = {} version_zero.updated_at = None version_zero.file_name = None MetadataModel.update_version_zero(metadata, version_zero, "c611a73c-12a0-4414-9ab5-ed1889122073") # check tags (value = empty string) did not make it in the actions assert "tags" not in [str(i.values[0]) for i in version_zero.mock_calls[0][2]["actions"] if i.format_string != "{0}"]
def test_validate_token(mocker): """ Test successful matching of secret """ token = "12345" plugin_stage = "dev" mocker.patch("src.plugin.metadata_model.MetadataModel.query", return_value=query_iter_obj(mocker, hash_token(token))) plugin_id = "test_plugin" MetadataModel.validate_token(token, plugin_id, plugin_stage)
def upload(plugin_id): """ End point for processing QGIS plugin data POSTed by the user If the query param "?stage" is not supplied, plugins POSTed are considered production. if `?stage=dev` is used the plugin is stored as a dev version :param data: plugin :type data: binary data :returns: tuple (http response, http code) :rtype: tuple (flask.wrappers.Response, int) """ plugin_stage = request.args.get("stage", DEFUALT_STAGE) validate_stage(plugin_stage) post_data = request.get_data() if not post_data: get_log().error("NoDataSupplied") raise DataError(400, "No plugin file supplied") # Get users access token from header token = get_access_token(request.headers) MetadataModel.validate_token(token, plugin_id, plugin_stage) # Test the file is a zipfile if not zipfile.is_zipfile(BytesIO(post_data)): get_log().error("NotZipfile") raise DataError(400, "Plugin file supplied not a Zipfile") # Extract plugin metadata plugin_zipfile = zipfile.ZipFile(BytesIO(post_data), "r", zipfile.ZIP_DEFLATED, False) metadata_path = plugin_parser.metadata_path(plugin_zipfile) metadata = plugin_parser.metadata_contents(plugin_zipfile, metadata_path) # Get the plugins root dir. This is what QGIS references when handling plugins g.plugin_id = plugin_parser.zipfile_root_dir(plugin_zipfile) if g.plugin_id != plugin_id: raise DataError(400, "Invalid plugin name %s" % g.plugin_id) # Allocate a filename filename = str(uuid.uuid4()) get_log().info("FileName", filename=filename) # Upload the plugin to s3 aws.s3_put(post_data, repo_bucket_name, filename, g.plugin_id) get_log().info("UploadedTos3", filename=filename, bucketName=repo_bucket_name) # Update metadata database try: plugin_metadata = MetadataModel.new_plugin_version(metadata, g.plugin_id, filename, plugin_stage) except ValueError as error: raise DataError(400, str(error)) return format_response(plugin_metadata, 201)
def test_validate_token_incorrect_secret(mocker): """ Fail if token does not match database secret """ mocker.patch("src.plugin.metadata_model.MetadataModel.query", return_value=query_iter_obj(mocker, "54321")) mocker.patch("werkzeug.local.LocalProxy.__getattr__", return_value={"authorization": "basic 12345"}) mocker.patch("src.plugin.log.g", return_value={"requestId": "1234567", "plugin_id": "test_plugin"}) token = "12345" plugin_id = "test_plugin" plugin_stage = "dev" with pytest.raises(DataError) as error: MetadataModel.validate_token(token, plugin_id, plugin_stage) assert "Invalid token" in str(error.value)
def test_metadata_model_missing_required(mocker): """ pynamdodb model throws exception on save if required fields are null """ mocker.patch("pynamodb.connection.base.get_session") mocker.patch("pynamodb.connection.table.Connection") metadata = { "id": "c611a73c-12a0-4414-9ab5-ed1889122073", "name": "test", "qgisMinimumVersion": "0.0.0", "qgisMaximumVersion": "0.0.1", "description": "this is a test", "about": "testing", "version": "1.0.0", "author": "me", "email": "*****@*****.**", } result = MetadataModel( id=metadata.get("id"), updated_at=metadata.get("updated_at"), name=metadata.get("name"), qgis_minimum_version=metadata.get("qgisMinimumVersion"), qgis_maximum_version=metadata.get("qgisMaximumVersion"), description=metadata.get("description"), about=metadata.get("about"), version=metadata.get("version"), author_name=metadata.get("author"), email=metadata.get("email"), changelog=metadata.get("changelog"), experimental=metadata.get("experimental"), deprecated=metadata.get("deprecated"), tags=metadata.get("tags"), homepage=metadata.get("homepage"), repository=metadata.get("repository"), tracker=metadata.get("tracker"), icon=metadata.get("icon"), category=metadata.get("category"), ) with pytest.raises(ValueError) as excinfo: result.save() assert "ValueError" in str(excinfo.value)
def health(): """ Ping to confirm the service is up """ checks = {} # check database connection MetadataModel.all_version_zeros(DEFUALT_STAGE) checks["db"] = {"status": "ok"} # check s3 connection aws.s3_head_bucket(repo_bucket_name) checks["s3"] = {"status": "ok"} # Anything not a 200 has been caught as an # error and the health-checks have failed get_log().info({"status": "ok", "details": checks}) return format_response({"status": "ok"}, 200)
def get_all_plugins(): """ List all plugin's metadata :returns: tuple (http response, http code) :rtype: tuple (flask.wrappers.Response, int) """ plugin_stage = request.args.get("stage", DEFUALT_STAGE) response = list(MetadataModel.all_version_zeros(plugin_stage)) return format_response(response, 200)
def archive(plugin_id): """ Takes a plugin_id as input and adds an end-date to the metadata record associated to the Id :param plugin_id: plugin_id :type data: string :returns: tuple (http response, http code) :rtype: tuple (flask.wrappers.Response, int) """ plugin_stage = request.args.get("stage", DEFUALT_STAGE) g.plugin_id = plugin_id # Get users access token from header token = get_access_token(request.headers) # validate access token MetadataModel.validate_token(token, g.plugin_id, plugin_stage) # Archive plugins response = MetadataModel.archive_plugin(plugin_id, plugin_stage) return format_response(response, 200)
def get_all_revisions(plugin_id): """ Takes a plugin_id and returns all associated plugin revisions :param plugin_id: plugin_id :type data: string :returns: tuple (http response, http code) :rtype: tuple (flask.wrappers.Response, int) """ plugin_stage = request.args.get("stage", DEFUALT_STAGE) g.plugin_id = plugin_id return format_response(MetadataModel.plugin_all_versions(plugin_id, plugin_stage), 200)
def get_plugin(plugin_id): """ Takes a plugin_id as input and returns the metadata of the one associated plugin to this Id :param plugin_id: plugin_id :type data: string :returns: tuple (http response, http code) :rtype: tuple (flask.wrappers.Response, int) """ plugin_stage = request.args.get("stage", DEFUALT_STAGE) g.plugin_id = plugin_id return format_response(MetadataModel.plugin_version_zero(plugin_id, plugin_stage), 200)
def generate_xml_body(repo_bucket_name, aws_region, qgis_version, plugin_stage): """ Generate XML describing plugin store from dynamodb plugin metadata db :param repo_bucket_name: s3 bucket name :type repo_bucket_name: string :param aws_region: aws_region :type aws_region: string :returns: string representation of plugin xml :rtype: string """ current_plugins = filter( lambda item: compatible_with_qgis_version(item, qgis_version), MetadataModel.all_version_zeros(plugin_stage)) root = ET.Element("plugins") for plugin in current_plugins: if not plugin["revisions"]: continue current_group = ET.SubElement(root, "pyqgis_plugin", { "name": plugin["name"], "version": plugin["version"] }) for key, value in plugin.items(): if key not in ("file_name", "name", "id", "category", "email", "item_version", "stage"): new_element = new_xml_element(key, value) current_group.append(new_element) new_element = new_xml_element( "file_name", f"{plugin['id']}.{plugin['version']}.zip") current_group.append(new_element) download_url = generate_download_url(repo_bucket_name, aws_region, plugin["file_name"]) new_element = new_xml_element("download_url", download_url) current_group.append(new_element) return ET.tostring(root)
def test_metadata_model(mocker): """ model parameters are set and save is executed without error """ mocker.patch("pynamodb.connection.base.get_session") mocker.patch("pynamodb.connection.table.Connection") file_name = str(uuid.uuid4()) metadata = { "id": "test_plugin", "item_version": "000000", "revisions": "0", "name": "test", "qgis_minimum_version": "0.0.0", "qgis_maximum_version": "0.0.1", "description": "this is a test", "about": "testing", "version": "1.0.0", "author_name": "me", "email": "*****@*****.**", "changelog": "done stuff", "experimental": "True", "deprecated": "False", "tags": "test", "homepage": "plugin.com", "repository": "github/plugin", "tracker": "github/pluigin/issues", "icon": "icon.png", "category": "test", "file_name": file_name, } result = MetadataModel( id=metadata.get("id"), item_version=metadata.get("item_version"), revisions=metadata.get("revisions"), name=metadata.get("name"), qgis_minimum_version=metadata.get("qgis_minimum_version"), qgis_maximum_version=metadata.get("qgis_maximum_version"), description=metadata.get("description"), about=metadata.get("about"), version=metadata.get("version"), author_name=metadata.get("author_name"), email=metadata.get("email"), changelog=metadata.get("changelog"), experimental=metadata.get("experimental"), deprecated=metadata.get("deprecated"), tags=metadata.get("tags"), homepage=metadata.get("homepage"), repository=metadata.get("repository"), tracker=metadata.get("tracker"), icon=metadata.get("icon"), category=metadata.get("category"), file_name=metadata.get("file_name"), ) result.save() for key in metadata: assert result.attribute_values[key] == metadata[key]