From f7924ad47af2f226a5fccf1bf24eaa70fa37b6fc Mon Sep 17 00:00:00 2001 From: Gerard Alonso Date: Thu, 3 Aug 2023 15:15:46 +0200 Subject: [PATCH] LITE-28258: list and export streams --- connect/cli/core/utils.py | 7 + connect/cli/plugins/commerce/commands.py | 106 ++++++ connect/cli/plugins/commerce/utils.py | 353 ++++++++++++++++++ poetry.lock | 140 +++---- pyproject.toml | 3 +- .../commerce/attachments_response.json | 76 ++++ .../commerce/billing_streams_response.json | 118 ++++++ tests/fixtures/commerce/columns_response.json | 96 +++++ .../commerce/pricing_streams_response.json | 137 +++++++ .../commerce/stream_retrieve_response.json | 74 ++++ .../commerce/transformations_response.json | 117 ++++++ tests/plugins/commerce/__init__.py | 0 tests/plugins/commerce/test_commands.py | 288 ++++++++++++++ tests/plugins/commerce/test_utils.py | 90 +++++ 14 files changed, 1523 insertions(+), 82 deletions(-) create mode 100644 connect/cli/plugins/commerce/commands.py create mode 100644 connect/cli/plugins/commerce/utils.py create mode 100644 tests/fixtures/commerce/attachments_response.json create mode 100644 tests/fixtures/commerce/billing_streams_response.json create mode 100644 tests/fixtures/commerce/columns_response.json create mode 100644 tests/fixtures/commerce/pricing_streams_response.json create mode 100644 tests/fixtures/commerce/stream_retrieve_response.json create mode 100644 tests/fixtures/commerce/transformations_response.json create mode 100644 tests/plugins/commerce/__init__.py create mode 100644 tests/plugins/commerce/test_commands.py create mode 100644 tests/plugins/commerce/test_utils.py diff --git a/connect/cli/core/utils.py b/connect/cli/core/utils.py index e56aec43..1f92500e 100644 --- a/connect/cli/core/utils.py +++ b/connect/cli/core/utils.py @@ -82,6 +82,13 @@ def validate_output_options(output_path, output_file, default_dir_name, default_ output_file = os.path.join(output_path, output_file or f'{default_file_name}.xlsx') + if os.path.exists(output_file): + console.confirm( + f'Are you sure you want to override the file {output_file} ?', + abort=True, + ) + console.echo('') + return output_file diff --git a/connect/cli/plugins/commerce/commands.py b/connect/cli/plugins/commerce/commands.py new file mode 100644 index 00000000..7349ba84 --- /dev/null +++ b/connect/cli/plugins/commerce/commands.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- + +# This file is part of the Ingram Micro Cloud Blue Connect connect-cli. +# Copyright (c) 2023 Ingram Micro. All Rights Reserved. + +import click +from connect.client import R + +from connect.cli.core import group +from connect.cli.core.config import pass_config +from connect.cli.core.terminal import console +from connect.cli.plugins.commerce.utils import ( + display_streams_table, + export_stream, +) + + +@group(name='commerce', short_help='Manage commerce definitions.') +def grp_commerce(): + pass # pragma: no cover + + +@grp_commerce.group( + name='stream', + short_help='Manage commerce streams.', +) +def grp_commerce_streams(): + pass # pragma: no cover + + +@grp_commerce_streams.command( + name='list', + short_help='List commerce billing and pricing streams.', +) +@click.option( + '--query', + '-q', + 'query', + help='RQL query expression.', +) +@pass_config +def cmd_list_streams(config, query): + query = query or R() + display_streams_table( + config.active.client.ns( + 'billing', + ) + .streams.filter( + query, + ) + .limit( + console.page_size, + ) + .select( + 'context', + 'sources', + ), + config.active.client.ns( + 'pricing', + ) + .streams.filter( + query, + ) + .limit( + console.page_size, + ) + .select( + 'context', + 'sources', + ), + config.active.id, + ) + + +@grp_commerce_streams.command( + name='export', + short_help='Export commerce billing or pricing streams.', +) +@click.argument('stream_id', metavar='stream_id', nargs=1, required=True) # noqa: E304 +@click.option( + '--out', + '-o', + 'output_file', + type=click.Path(exists=False, file_okay=True, dir_okay=False), + help='Output Excel file name.', +) +@click.option( + '--output_path', + '-p', + 'output_path', + type=click.Path(exists=True, file_okay=False, dir_okay=True), + help='Directory where to store the export.', +) +@pass_config +def cmd_export_stream(config, stream_id, output_file, output_path): + export_stream( + client=config.active.client, + stream_id=stream_id, + active_account_id=config.active.id, + output_file=output_file, + output_path=output_path, + ) + + +def get_group(): + return grp_commerce diff --git a/connect/cli/plugins/commerce/utils.py b/connect/cli/plugins/commerce/utils.py new file mode 100644 index 00000000..732c8c73 --- /dev/null +++ b/connect/cli/plugins/commerce/utils.py @@ -0,0 +1,353 @@ +import os +import json +from datetime import datetime +import string +from urllib.parse import urlparse + +from click import ClickException +from connect.cli.core.terminal import console +from connect.client import ClientError +from openpyxl import Workbook +from openpyxl.styles import Alignment, Font, PatternFill +from openpyxl.styles.colors import WHITE, Color + +from connect.cli.core.utils import validate_output_options + + +def display_streams_table( + query_billing_streams, + query_pricing_streams, + active_account_id, +): + rows = [] + for origin, query in ( + ('Billing', query_billing_streams), + ('Pricing', query_pricing_streams), + ): + for resource in query: + rows.append( + ( + resource['id'], + origin, + resource['name'], + 'Computed' if ('sources' in resource and resource['sources']) else 'Simple', + 'Inbound' if resource['owner']['id'] != active_account_id else 'Outbound', + resource['status'].capitalize(), + resource['visibility'].capitalize(), + ), + ) + if rows: + console.table( + columns=[ + 'ID', + 'Business scope', + 'Name', + 'Type', + 'Category', + 'Status', + 'Visibility', + ], + rows=rows, + ) + else: + console.secho( + f'Results not found for the current account {active_account_id}.', + fg='yellow', + ) + + +def guess_if_billing_or_pricing_stream(client, stream_id): + if client.ns('billing').streams.filter(id=stream_id).count() > 0: + return 'billing' + elif client.ns('pricing').streams.filter(id=stream_id).count() > 0: + return 'pricing' + return None + + +def fill_general_information(ws, data, progress): + ws.title = 'General Information' + ws.column_dimensions['A'].width = 40 + ws.column_dimensions['B'].width = 180 + ws.merge_cells('A1:B1') + cell = ws['A1'] + cell.fill = PatternFill('solid', start_color=Color('1565C0')) + cell.font = Font(sz=24, color=WHITE) + cell.alignment = Alignment(horizontal='center', vertical='center') + cell.value = 'Stream information' + count = len(data) + task = progress.add_task('Filling general information', total=count) + for line, key in enumerate(data, 2): + ws[f'A{line}'] = key + ws[f'B{line}'] = data[key] + progress.update(task, advance=1) + + +def _fill_headers(ws, columns): + color = Color('d3d3d3') + fill = PatternFill('solid', color) + for n, value in enumerate(columns): + letter = string.ascii_uppercase[n] + cell = ws[f'{letter}1'] + cell.fill = fill + cell.value = value + + +def fill_columns(ws, columns, progress): + columns = list(columns) + ws.column_dimensions['A'].width = 22 + ws.column_dimensions['B'].width = 20 + ws.column_dimensions['C'].width = 40 + _fill_headers( + ws, + ( + 'ID', + 'Name', + 'Description', + 'Type', + 'Position', + 'Required', + 'Output', + ), + ) + alignment = Alignment( + horizontal='left', + vertical='top', + ) + if len(columns) > 0: + task = progress.add_task('Filling columns', total=len(columns)) + for n, col in enumerate(columns, 2): + ws.cell(n, 1, value=col['id']).alignment = alignment + ws.cell(n, 2, value=col['name']).alignment = alignment + ws.cell(n, 3, value=col.get('description', '')).alignment = alignment + ws.cell(n, 4, value=col.get('type', '')).alignment = alignment + ws.cell(n, 5, value=col['position']).alignment = alignment + ws.cell(n, 6, value=col['required']).alignment = alignment + ws.cell(n, 7, value=col['output']).alignment = alignment + progress.update(task, advance=1) + + +def fill_transformations(ws, transformations, progress): + try: + transformations = list(transformations) + except ClientError: + return + ws.column_dimensions['A'].width = 30 + for column in ('B', 'C', 'D', 'E', 'F', 'G'): + ws.column_dimensions[column].width = 22 + ws.column_dimensions['H'].width = 40 + _fill_headers( + ws, + ( + 'ID', + 'Function ID', + 'Function Name', + 'Description', + 'Overview', + 'Input columns', + 'Output Columns', + 'Position', + 'Settings', + ), + ) + alignment = Alignment( + horizontal='left', + vertical='top', + ) + if len(transformations) > 0: + task = progress.add_task('Filling transformations', total=len(transformations)) + for n, transformation in enumerate(transformations, 2): + ws.cell(n, 1, value=transformation['id']).alignment = alignment + ws.cell(n, 2, value=transformation['function']['id']).alignment = alignment + ws.cell(n, 3, value=transformation['function']['name']).alignment = alignment + ws.cell(n, 4, value=transformation.get('description', '')).alignment = alignment + ws.cell(n, 5, value=transformation['overview']).alignment = alignment + col_alignment = Alignment( + horizontal='left', + vertical='top', + wrap_text=True, + ) + ws.cell( + n, + 6, + value='\n'.join([c['id'] for c in transformation['columns']['input']]), + ).alignment = col_alignment + ws.cell( + n, + 7, + value='\n'.join([c['id'] for c in transformation['columns']['output']]), + ).alignment = col_alignment + ws.cell(n, 8, value=transformation['position']).alignment = alignment + ws.cell( + n, + 9, + value=json.dumps(transformation['settings'], indent=4, sort_keys=True), + ).alignment = col_alignment + ws.row_dimensions[n].height = 120 + progress.update(task, advance=1) + + +def _download_file(client, folder_type, folder_name, file_id, file_destination): + try: + response = ( + client.ns( + 'media', + ) + .ns( + 'folders', + ) + .collection( + folder_type, + )[folder_name] + .collection( + 'files', + )[file_id] + .get() + ) + except ClientError: + raise ClickException(f'Error obtaining file {file_id} -> {file_destination}') + with open(file_destination, 'wb') as f: + f.write(response) + + +def fill_and_download_attachments(ws, attachments, client, base_path, progress): + attachments = list(attachments) + ws.column_dimensions['A'].width = 30 + ws.column_dimensions['B'].width = 30 + _fill_headers( + ws, + ('ID', 'Name'), + ) + if len(attachments) > 0: + task = progress.add_task('Filling and downloading attachments', total=len(attachments)) + for n, attachment in enumerate(attachments, 2): + ws.cell(n, 1, value=attachment['id']) + ws.cell(n, 2, value=attachment['name']) + _download_file( + client=client, + folder_type=attachment['folder']['type'], + folder_name=attachment['folder']['name'], + file_id=attachment['id'], + file_destination=os.path.join(base_path, attachment['name']), + ) + progress.update(task, advance=1) + + +def export_stream( + client, + stream_id, + active_account_id, + output_file, + output_path=None, +): + output_file = validate_output_options(output_path, output_file, default_dir_name=stream_id) + attachments_path = os.path.join(os.path.dirname(output_file), 'attachments') + if not os.path.exists(attachments_path): + os.mkdir(attachments_path) + sample_input_path = os.path.join(os.path.dirname(output_file), 'sample', 'input') + if not os.path.exists(sample_input_path): + os.makedirs(sample_input_path) + + with console.status_progress() as (status, progress): + collection = guess_if_billing_or_pricing_stream(client, stream_id) + if not collection: + console.secho( + f'Stream {stream_id} not found for the current account {active_account_id}.', + fg='red', + ) + return + + response = ( + client.ns(collection) + .streams.filter(id=stream_id) + .select( + 'context', + 'samples', + 'sources', + ) + .first() + ) + + wb = Workbook() + + stream_type = 'Computed' if 'sources' in response and response['sources'] else 'Simple' + input = response['samples'].get('input', {}) + input_file_name = urlparse(input.get('name', '/')).path.split('/')[-1] + status.update('Extracting general information', fg='blue') + fill_general_information( + ws=wb.active, + data={ + 'Stream ID': stream_id, + 'Stream Name': response['name'], + 'Stream Description': response['description'], + 'Stream Type': stream_type, + 'Stream Category': 'Inbound' + if response['owner']['id'] != active_account_id + else 'Outbound', + 'Computed Stream Source ID': response['sources'][0]['id'] + if stream_type == 'Computed' + else '', + 'Computed Stream Source Name': response['sources'][0]['name'] + if stream_type == 'Computed' + else '', + 'Computed Stream Source Type': response['sources'][0]['type'] + if stream_type == 'Computed' + else '', + 'Product ID': response['context'].get('product', {}).get('id', ''), + 'Product Name': response['context'].get('product', {}).get('name', ''), + 'Partner ID': response['context'].get('account', {}).get('id', ''), + 'Partner Name': response['context'].get('account', {}).get('name', ''), + 'Marketplace ID': response['context'].get('marketplace', {}).get('id', ''), + 'Marketplace Name': response['context'].get('marketplace', {}).get('name', ''), + 'Pricelist ID': response['context'].get('pricelist', {}).get('id', ''), + 'Pricelist Name': response['context'].get('pricelist', {}).get('name', ''), + 'Listing ID': response['context'].get('listing', {}).get('id', ''), + 'Visibility': response['visibility'], + 'Input Data': input_file_name, + 'Export datetime': datetime.now().strftime('%Y-%m-%dT%H:%M:%M.%f'), + }, + progress=progress, + ) + + if input: + extracted_stream = input['name'].split('/')[-4] + status.update('Downloading sample file', fg='blue') + _download_file( + client=client, + folder_type='streams_samples', + folder_name=extracted_stream, + file_id=input.get('id', None), + file_destination=os.path.join(sample_input_path, input_file_name), + ) + + status.update('Extracting columns', fg='blue') + fill_columns( + wb.create_sheet('Columns'), + client.ns(collection).streams[stream_id].columns.all(), + progress=progress, + ) + + status.update('Extracting transformations', fg='blue') + fill_transformations( + wb.create_sheet('Transformations'), + client.ns(collection).streams[stream_id].transformations.all().select('columns'), + progress=progress, + ) + + status.update('Extracting attachments', fg='blue') + fill_and_download_attachments( + wb.create_sheet('Attachments'), + client.ns('media') + .ns('folders') + .collection('streams_attachments')[stream_id] + .files.all(), + client, + base_path=attachments_path, + progress=progress, + ) + + wb.save(output_file) + + console.secho( + f'Stream {stream_id} exported properly to {output_file}.', + fg='green', + ) diff --git a/poetry.lock b/poetry.lock index b72232bc..ab6fb5a2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -119,16 +119,6 @@ six = "*" [package.extras] test = ["astroid", "pytest"] -[[package]] -name = "atomicwrites" -version = "1.4.1" -description = "Atomic file writes." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] - [[package]] name = "attrs" version = "23.1.0" @@ -590,13 +580,13 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] [[package]] name = "connect-eaas-core" -version = "28.13" +version = "28.14" description = "Connect Eaas Core" optional = false python-versions = ">=3.8,<4" files = [ - {file = "connect_eaas_core-28.13-py3-none-any.whl", hash = "sha256:243c881b3fba346225e01cf6fdc1dc49a9f514ae7820a1c121a51ec4c4a51bf2"}, - {file = "connect_eaas_core-28.13.tar.gz", hash = "sha256:ef720ebee97f570e90cc90ae69eecb3214b7a5d3db0079515b607ac4f73571e3"}, + {file = "connect_eaas_core-28.14-py3-none-any.whl", hash = "sha256:70fbe68d3fe0f96d8ccb52d02b3c09dd6cc77039da5a0f5ba7acdf2c7e53535b"}, + {file = "connect_eaas_core-28.14.tar.gz", hash = "sha256:664a3ca516820565b4bad59131089b01eba27199baca3ad98a1914c46b34428b"}, ] [package.dependencies] @@ -1074,45 +1064,45 @@ flake8 = "*" [[package]] name = "fonttools" -version = "4.41.1" +version = "4.42.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.41.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a7bbb290d13c6dd718ec2c3db46fe6c5f6811e7ea1e07f145fd8468176398224"}, - {file = "fonttools-4.41.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec453a45778524f925a8f20fd26a3326f398bfc55d534e37bab470c5e415caa1"}, - {file = "fonttools-4.41.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2071267deaa6d93cb16288613419679c77220543551cbe61da02c93d92df72f"}, - {file = "fonttools-4.41.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e3334d51f0e37e2c6056e67141b2adabc92613a968797e2571ca8a03bd64773"}, - {file = "fonttools-4.41.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cac73bbef7734e78c60949da11c4903ee5837168e58772371bd42a75872f4f82"}, - {file = "fonttools-4.41.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:edee0900cf0eedb29d17c7876102d6e5a91ee333882b1f5abc83e85b934cadb5"}, - {file = "fonttools-4.41.1-cp310-cp310-win32.whl", hash = "sha256:2a22b2c425c698dcd5d6b0ff0b566e8e9663172118db6fd5f1941f9b8063da9b"}, - {file = "fonttools-4.41.1-cp310-cp310-win_amd64.whl", hash = "sha256:547ab36a799dded58a46fa647266c24d0ed43a66028cd1cd4370b246ad426cac"}, - {file = "fonttools-4.41.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:849ec722bbf7d3501a0e879e57dec1fc54919d31bff3f690af30bb87970f9784"}, - {file = "fonttools-4.41.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:38cdecd8f1fd4bf4daae7fed1b3170dfc1b523388d6664b2204b351820aa78a7"}, - {file = "fonttools-4.41.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ae64303ba670f8959fdaaa30ba0c2dabe75364fdec1caeee596c45d51ca3425"}, - {file = "fonttools-4.41.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f14f3ccea4cc7dd1b277385adf3c3bf18f9860f87eab9c2fb650b0af16800f55"}, - {file = "fonttools-4.41.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:33191f062549e6bb1a4782c22a04ebd37009c09360e2d6686ac5083774d06d95"}, - {file = "fonttools-4.41.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:704bccd69b0abb6fab9f5e4d2b75896afa48b427caa2c7988792a2ffce35b441"}, - {file = "fonttools-4.41.1-cp311-cp311-win32.whl", hash = "sha256:4edc795533421e98f60acee7d28fc8d941ff5ac10f44668c9c3635ad72ae9045"}, - {file = "fonttools-4.41.1-cp311-cp311-win_amd64.whl", hash = "sha256:aaaef294d8e411f0ecb778a0aefd11bb5884c9b8333cc1011bdaf3b58ca4bd75"}, - {file = "fonttools-4.41.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3d1f9471134affc1e3b1b806db6e3e2ad3fa99439e332f1881a474c825101096"}, - {file = "fonttools-4.41.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:59eba8b2e749a1de85760da22333f3d17c42b66e03758855a12a2a542723c6e7"}, - {file = "fonttools-4.41.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9b3cc10dc9e0834b6665fd63ae0c6964c6bc3d7166e9bc84772e0edd09f9fa2"}, - {file = "fonttools-4.41.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2c2964bdc827ba6b8a91dc6de792620be4da3922c4cf0599f36a488c07e2b2"}, - {file = "fonttools-4.41.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7763316111df7b5165529f4183a334aa24c13cdb5375ffa1dc8ce309c8bf4e5c"}, - {file = "fonttools-4.41.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b2d1ee95be42b80d1f002d1ee0a51d7a435ea90d36f1a5ae331be9962ee5a3f1"}, - {file = "fonttools-4.41.1-cp38-cp38-win32.whl", hash = "sha256:f48602c0b3fd79cd83a34c40af565fe6db7ac9085c8823b552e6e751e3a5b8be"}, - {file = "fonttools-4.41.1-cp38-cp38-win_amd64.whl", hash = "sha256:b0938ebbeccf7c80bb9a15e31645cf831572c3a33d5cc69abe436e7000c61b14"}, - {file = "fonttools-4.41.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e5c2b0a95a221838991e2f0e455dec1ca3a8cc9cd54febd68cc64d40fdb83669"}, - {file = "fonttools-4.41.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:891cfc5a83b0307688f78b9bb446f03a7a1ad981690ac8362f50518bc6153975"}, - {file = "fonttools-4.41.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73ef0bb5d60eb02ba4d3a7d23ada32184bd86007cb2de3657cfcb1175325fc83"}, - {file = "fonttools-4.41.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f240d9adf0583ac8fc1646afe7f4ac039022b6f8fa4f1575a2cfa53675360b69"}, - {file = "fonttools-4.41.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bdd729744ae7ecd7f7311ad25d99da4999003dcfe43b436cf3c333d4e68de73d"}, - {file = "fonttools-4.41.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b927e5f466d99c03e6e20961946314b81d6e3490d95865ef88061144d9f62e38"}, - {file = "fonttools-4.41.1-cp39-cp39-win32.whl", hash = "sha256:afce2aeb80be72b4da7dd114f10f04873ff512793d13ce0b19d12b2a4c44c0f0"}, - {file = "fonttools-4.41.1-cp39-cp39-win_amd64.whl", hash = "sha256:1df1b6f4c7c4bc8201eb47f3b268adbf2539943aa43c400f84556557e3e109c0"}, - {file = "fonttools-4.41.1-py3-none-any.whl", hash = "sha256:952cb405f78734cf6466252fec42e206450d1a6715746013f64df9cbd4f896fa"}, - {file = "fonttools-4.41.1.tar.gz", hash = "sha256:e16a9449f21a93909c5be2f5ed5246420f2316e94195dbfccb5238aaa38f9751"}, + {file = "fonttools-4.42.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9c456d1f23deff64ffc8b5b098718e149279abdea4d8692dba69172fb6a0d597"}, + {file = "fonttools-4.42.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:150122ed93127a26bc3670ebab7e2add1e0983d30927733aec327ebf4255b072"}, + {file = "fonttools-4.42.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48e82d776d2e93f88ca56567509d102266e7ab2fb707a0326f032fe657335238"}, + {file = "fonttools-4.42.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58c1165f9b2662645de9b19a8c8bdd636b36294ccc07e1b0163856b74f10bafc"}, + {file = "fonttools-4.42.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d6dc3fa91414ff4daa195c05f946e6a575bd214821e26d17ca50f74b35b0fe4"}, + {file = "fonttools-4.42.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fae4e801b774cc62cecf4a57b1eae4097903fced00c608d9e2bc8f84cd87b54a"}, + {file = "fonttools-4.42.0-cp310-cp310-win32.whl", hash = "sha256:b8600ae7dce6ec3ddfb201abb98c9d53abbf8064d7ac0c8a0d8925e722ccf2a0"}, + {file = "fonttools-4.42.0-cp310-cp310-win_amd64.whl", hash = "sha256:57b68eab183fafac7cd7d464a7bfa0fcd4edf6c67837d14fb09c1c20516cf20b"}, + {file = "fonttools-4.42.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0a1466713e54bdbf5521f2f73eebfe727a528905ff5ec63cda40961b4b1eea95"}, + {file = "fonttools-4.42.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3fb2a69870bfe143ec20b039a1c8009e149dd7780dd89554cc8a11f79e5de86b"}, + {file = "fonttools-4.42.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae881e484702efdb6cf756462622de81d4414c454edfd950b137e9a7352b3cb9"}, + {file = "fonttools-4.42.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27ec3246a088555629f9f0902f7412220c67340553ca91eb540cf247aacb1983"}, + {file = "fonttools-4.42.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ece1886d12bb36c48c00b2031518877f41abae317e3a55620d38e307d799b7e"}, + {file = "fonttools-4.42.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:10dac980f2b975ef74532e2a94bb00e97a95b4595fb7f98db493c474d5f54d0e"}, + {file = "fonttools-4.42.0-cp311-cp311-win32.whl", hash = "sha256:83b98be5d291e08501bd4fc0c4e0f8e6e05b99f3924068b17c5c9972af6fff84"}, + {file = "fonttools-4.42.0-cp311-cp311-win_amd64.whl", hash = "sha256:e35bed436726194c5e6e094fdfb423fb7afaa0211199f9d245e59e11118c576c"}, + {file = "fonttools-4.42.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c36c904ce0322df01e590ba814d5d69e084e985d7e4c2869378671d79662a7d4"}, + {file = "fonttools-4.42.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d54e600a2bcfa5cdaa860237765c01804a03b08404d6affcd92942fa7315ffba"}, + {file = "fonttools-4.42.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01cfe02416b6d416c5c8d15e30315cbcd3e97d1b50d3b34b0ce59f742ef55258"}, + {file = "fonttools-4.42.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f81ed9065b4bd3f4f3ce8e4873cd6a6b3f4e92b1eddefde35d332c6f414acc3"}, + {file = "fonttools-4.42.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:685a4dd6cf31593b50d6d441feb7781a4a7ef61e19551463e14ed7c527b86f9f"}, + {file = "fonttools-4.42.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:329341ba3d86a36e482610db56b30705384cb23bd595eac8cbb045f627778e9d"}, + {file = "fonttools-4.42.0-cp38-cp38-win32.whl", hash = "sha256:4655c480a1a4d706152ff54f20e20cf7609084016f1df3851cce67cef768f40a"}, + {file = "fonttools-4.42.0-cp38-cp38-win_amd64.whl", hash = "sha256:6bd7e4777bff1dcb7c4eff4786998422770f3bfbef8be401c5332895517ba3fa"}, + {file = "fonttools-4.42.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9b55d2a3b360e0c7fc5bd8badf1503ca1c11dd3a1cd20f2c26787ffa145a9c7"}, + {file = "fonttools-4.42.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0df8ef75ba5791e873c9eac2262196497525e3f07699a2576d3ab9ddf41cb619"}, + {file = "fonttools-4.42.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd2363ea7728496827658682d049ffb2e98525e2247ca64554864a8cc945568"}, + {file = "fonttools-4.42.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d40673b2e927f7cd0819c6f04489dfbeb337b4a7b10fc633c89bf4f34ecb9620"}, + {file = "fonttools-4.42.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c8bf88f9e3ce347c716921804ef3a8330cb128284eb6c0b6c4b3574f3c580023"}, + {file = "fonttools-4.42.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:703101eb0490fae32baf385385d47787b73d9ea55253df43b487c89ec767e0d7"}, + {file = "fonttools-4.42.0-cp39-cp39-win32.whl", hash = "sha256:f0290ea7f9945174bd4dfd66e96149037441eb2008f3649094f056201d99e293"}, + {file = "fonttools-4.42.0-cp39-cp39-win_amd64.whl", hash = "sha256:ae7df0ae9ee2f3f7676b0ff6f4ebe48ad0acaeeeaa0b6839d15dbf0709f2c5ef"}, + {file = "fonttools-4.42.0-py3-none-any.whl", hash = "sha256:dfe7fa7e607f7e8b58d0c32501a3a7cac148538300626d1b930082c90ae7f6bd"}, + {file = "fonttools-4.42.0.tar.gz", hash = "sha256:614b1283dca88effd20ee48160518e6de275ce9b5456a3134d5f235523fc5065"}, ] [package.dependencies] @@ -1475,21 +1465,21 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] [[package]] name = "jedi" -version = "0.18.2" +version = "0.19.0" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" files = [ - {file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"}, - {file = "jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"}, + {file = "jedi-0.19.0-py2.py3-none-any.whl", hash = "sha256:cb8ce23fbccff0025e9386b5cf85e892f94c9b822378f8da49970471335ac64e"}, + {file = "jedi-0.19.0.tar.gz", hash = "sha256:bcf9894f1753969cbac8022a8c2eaee06bfa3724e4192470aaffe7eb6272b0c4"}, ] [package.dependencies] -parso = ">=0.8.0,<0.9.0" +parso = ">=0.8.3,<0.9.0" [package.extras] docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] [[package]] @@ -1908,13 +1898,13 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathspec" -version = "0.11.1" +version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, - {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] @@ -2024,18 +2014,18 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa [[package]] name = "platformdirs" -version = "3.9.1" +version = "3.10.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"}, - {file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "plotly" @@ -2117,17 +2107,6 @@ files = [ [package.extras] tests = ["pytest"] -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] - [[package]] name = "pycodestyle" version = "2.9.1" @@ -2295,27 +2274,26 @@ files = [ [[package]] name = "pytest" -version = "6.2.5" +version = "7.2.2" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, + {file = "pytest-7.2.2-py3-none-any.whl", hash = "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e"}, + {file = "pytest-7.2.2.tar.gz", hash = "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4"}, ] [package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -toml = "*" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] [[package]] name = "pytest-cov" @@ -3001,4 +2979,4 @@ test = ["pytest"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4" -content-hash = "d4adf3a9aae31658584edbb2df249fcafa8e64ed17f1dd4204aa2f1bebfabf12" +content-hash = "7d45db6718f963fc51d7d64d8873faa0c9a57737bbacbe4f24d0e56c19c9a02f" diff --git a/pyproject.toml b/pyproject.toml index 3246aead..07886942 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ ccli = 'connect.cli.ccli:main' "report" = "connect.cli.plugins.report.commands:get_group" "play" = "connect.cli.plugins.play.commands:get_group" "translation" = "connect.cli.plugins.translation.commands:get_group" +"commerce" = "connect.cli.plugins.commerce.commands:get_group" [tool.poetry.dependencies] @@ -76,7 +77,7 @@ pyyaml = "^6.0" [tool.poetry.group.test.dependencies] black = "23.*" -pytest = "^6.1.2" +pytest = "7.2.*" pytest-cov = "^2.10.1" pytest-mock = "^3.3.1" freezegun = "^1.2.1" diff --git a/tests/fixtures/commerce/attachments_response.json b/tests/fixtures/commerce/attachments_response.json new file mode 100644 index 00000000..17ffbcaf --- /dev/null +++ b/tests/fixtures/commerce/attachments_response.json @@ -0,0 +1,76 @@ +[ + { + "id": "MFL-2481-0572-9001", + "file": "/public/v1/media/folders/streams_attachments/STR-7755-7115-2464/files/MFL-2481-0572-9001/for_testing_auxiliar_1.xlsx", + "size": 9599, + "folder": { + "name": "STR-7755-7115-2464", + "type": "streams_attachments" + }, + "owner": { + "id": "VA-050-000", + "name": "Vendor account 00" + }, + "name": "for_testing_auxiliar_1.xlsx", + "mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "events": { + "created": { + "at": "2023-07-31T15:24:18+00:00", + "by": { + "id": "UR-050-000-003", + "name": "Gerard" + } + }, + "confirmed": { + "at": "2023-07-31T15:24:18+00:00", + "by": { + "id": "UR-050-000-003", + "name": "Gerard" + } + } + }, + "access": { + "VA-050-000": { + "view": true, + "delete": true + } + } + }, + { + "id": "MFL-8784-6884-8192", + "file": "/public/v1/media/folders/streams_attachments/STR-7755-7115-2464/files/MFL-8784-6884-8192/demo_styles.xlsx", + "size": 10005, + "folder": { + "name": "STR-7755-7115-2464", + "type": "streams_attachments" + }, + "owner": { + "id": "VA-050-000", + "name": "Vendor account 00" + }, + "name": "demo_styles.xlsx", + "mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "events": { + "created": { + "at": "2023-07-31T15:25:58+00:00", + "by": { + "id": "UR-050-000-003", + "name": "Gerard" + } + }, + "confirmed": { + "at": "2023-07-31T15:25:58+00:00", + "by": { + "id": "UR-050-000-003", + "name": "Gerard" + } + } + }, + "access": { + "VA-050-000": { + "view": true, + "delete": true + } + } + } +] diff --git a/tests/fixtures/commerce/billing_streams_response.json b/tests/fixtures/commerce/billing_streams_response.json new file mode 100644 index 00000000..246ca710 --- /dev/null +++ b/tests/fixtures/commerce/billing_streams_response.json @@ -0,0 +1,118 @@ +[ + { + "id": "STR-6250-2064-0382", + "name": "My simple billing from source", + "status": "configuring", + "description": "Some description", + "sources": [ + { + "id": "STR-8722-8683-0414", + "name": "Simple Billing stream", + "type": "stream", + "status": "active" + } + ], + "visibility": "private", + "samples": { + "input": { + "id": "MFL-1959-8863-6077", + "name": "/public/v1/media/folders/streams_samples/STR-8722-8683-0414/files/MFL-1959-8863-6077/STR-8722-8683-0414-out.xlsx" + }, + "output": { + "id": "MFL-4465-0608-6466", + "name": "/public/v1/media/folders/streams_samples/STR-6250-2064-0382/files/MFL-4465-0608-6466/STR-6250-2064-0382-out.xlsx" + } + }, + "owner": { + "id": "PA-050-101", + "name": "Provider account 01" + }, + "type": "billing", + "context": { + "account": { + "id": "VA-050-000", + "name": "Vendor account 00" + }, + "product": { + "id": "PRD-054-258-626", + "name": "Product P03", + "icon": "/media/VA-050-000/PRD-054-258-626/media/PRD-054-258-626-logo.png" + }, + "marketplace": { + "id": "MP-05011", + "name": "Marketplace M11", + "icon": "/media/PA-050-101/marketplaces/MP-05011/icon.png", + "currency": "USD" + }, + "listing": { + "id": "LST-760-660-960" + } + } + }, + { + "id": "STR-7755-0103-9379", + "name": "My simple billing stream", + "status": "active", + "description": "Some description", + "sources": [], + "visibility": "private", + "samples": { + "input": { + "id": "MFL-7603-5277-2030", + "name": "/public/v1/media/folders/streams_samples/STR-7755-0103-9379/files/MFL-7603-5277-2030/demo_styles.xlsx" + }, + "output": { + "id": "MFL-4878-5295-3893", + "name": "/public/v1/media/folders/streams_samples/STR-7755-0103-9379/files/MFL-4878-5295-3893/STR-7755-0103-9379-out.xlsx" + } + }, + "owner": { + "id": "VA-000", + "name": "Provider account 01" + }, + "type": "billing", + "context": {} + }, + { + "id": "STR-8722-8683-0414", + "name": "Simple Billing stream", + "status": "active", + "description": "Some description", + "visibility": "published", + "samples": { + "input": { + "id": "MFL-9059-7665-2037", + "name": "/public/v1/media/folders/streams_samples/STR-7755-7115-2464/files/MFL-9059-7665-2037/for_test.xlsx" + }, + "output": { + "id": "MFL-1959-8863-6077", + "name": "/public/v1/media/folders/streams_samples/STR-8722-8683-0414/files/MFL-1959-8863-6077/STR-8722-8683-0414-out.xlsx" + } + }, + "owner": { + "id": "VA-050-000", + "name": "Vendor account 00" + }, + "type": "billing", + "context": { + "account": { + "id": "PA-050-101", + "name": "Provider account 01 for galonso" + }, + "product": { + "id": "PRD-054-258-626", + "name": "Product P03", + "icon": "/media/VA-050-000/PRD-054-258-626/media/PRD-054-258-626-logo.png" + }, + "marketplace": { + "id": "MP-05011", + "name": "Marketplace M11 for galonso", + "icon": "/media/PA-050-101/marketplaces/MP-05011/icon.png", + "currency": "USD" + }, + "listing": { + "id": "LST-760-660-960" + } + } + } +] \ No newline at end of file diff --git a/tests/fixtures/commerce/columns_response.json b/tests/fixtures/commerce/columns_response.json new file mode 100644 index 00000000..6731001c --- /dev/null +++ b/tests/fixtures/commerce/columns_response.json @@ -0,0 +1,96 @@ +[ + { + "id": "SCOL-7755-7115-2464-001", + "name": "Customer Name", + "type": "string", + "position": 10000, + "nullable": true, + "required": false, + "output": true + }, + { + "id": "SCOL-7755-7115-2464-002", + "name": "Subscription ID", + "type": "string", + "position": 20000, + "nullable": true, + "required": false, + "output": true, + "description": "Subscription ID column" + }, + { + "id": "SCOL-7755-7115-2464-003", + "name": "Unexpected Column", + "type": "string", + "position": 30000, + "nullable": true, + "required": false, + "output": true + }, + { + "id": "SCOL-7755-7115-2464-004", + "name": "copy column", + "type": "string", + "position": 40000, + "nullable": true, + "required": false, + "output": true + }, + { + "id": "SCOL-7755-7115-2464-005", + "name": "split column", + "type": "string", + "position": 50000, + "nullable": true, + "required": false, + "output": true + }, + { + "id": "SCOL-7755-7115-2464-006", + "name": "currency converter", + "type": "decimal", + "constraints": { + "precision": 2 + }, + "position": 60000, + "nullable": true, + "required": false, + "output": true + }, + { + "id": "SCOL-7755-7115-2464-007", + "name": "decimal string", + "type": "string", + "position": 70000, + "nullable": true, + "required": false, + "output": true + }, + { + "id": "SCOL-7755-7115-2464-008", + "name": "integer", + "type": "integer", + "position": 80000, + "nullable": true, + "required": false, + "output": true + }, + { + "id": "SCOL-7755-7115-2464-009", + "name": "string", + "type": "string", + "position": 90000, + "nullable": true, + "required": false, + "output": true + }, + { + "id": "SCOL-7755-7115-2464-010", + "name": "datetime", + "type": "datetime", + "position": 100000, + "nullable": true, + "required": false, + "output": true + } +] \ No newline at end of file diff --git a/tests/fixtures/commerce/pricing_streams_response.json b/tests/fixtures/commerce/pricing_streams_response.json new file mode 100644 index 00000000..f6dc6032 --- /dev/null +++ b/tests/fixtures/commerce/pricing_streams_response.json @@ -0,0 +1,137 @@ +[ + { + "id": "STR-4047-7247-4594", + "name": "My simple pricing stream", + "status": "configuring", + "description": "Some description", + "sources": [], + "visibility": "private", + "samples": { + "input": { + "id": "MFL-8463-2536-6370", + "name": "/public/v1/media/folders/streams_samples/STR-4047-7247-4594/files/MFL-8463-2536-6370/demo_styles.xlsx" + }, + "output": { + "id": "MFL-1578-8917-0820", + "name": "/public/v1/media/folders/streams_samples/STR-4047-7247-4594/files/MFL-1578-8917-0820/STR-4047-7247-4594-out.xlsx" + } + }, + "owner": { + "id": "VA-000", + "name": "Provider account 01" + }, + "type": "pricing", + "context": { + "account": { + "id": "VA-050-000", + "name": "Vendor account 00 for galonso" + }, + "product": { + "id": "PRD-054-258-626", + "name": "Product P03", + "icon": "/media/VA-050-000/PRD-054-258-626/media/PRD-054-258-626-logo.png" + }, + "marketplace": { + "id": "MP-05011", + "name": "Marketplace M11 for galonso", + "icon": "/media/PA-050-101/marketplaces/MP-05011/icon.png", + "currency": "USD" + }, + "listing": { + "id": "LST-760-660-960" + } + } + }, + { + "id": "STR-5066-5885-2990", + "name": "My computed pricing from pricing stream", + "status": "active", + "description": "Some description", + "sources": [ + { + "id": "STR-7755-7115-2464", + "name": "Simple pricing stream", + "type": "stream", + "status": "active" + } + ], + "visibility": "private", + "samples": { + "input": { + "id": "MFL-7908-1914-3192", + "name": "/public/v1/media/folders/streams_samples/STR-7755-7115-2464/files/MFL-7908-1914-3192/STR-7755-7115-2464-out.xlsx" + }, + "output": { + "id": "MFL-6928-7852-4876", + "name": "/public/v1/media/folders/streams_samples/STR-5066-5885-2990/files/MFL-6928-7852-4876/STR-5066-5885-2990-out.xlsx" + } + }, + "owner": { + "id": "PA-050-101", + "name": "Provider account 01" + }, + "type": "pricing", + "context": { + "account": { + "id": "VA-050-000", + "name": "Vendor account 00" + }, + "product": { + "id": "PRD-054-258-626", + "name": "Product P03", + "icon": "/media/VA-050-000/PRD-054-258-626/media/PRD-054-258-626-logo.png" + }, + "marketplace": { + "id": "MP-05011", + "name": "Marketplace M11 for galonso", + "icon": "/media/PA-050-101/marketplaces/MP-05011/icon.png", + "currency": "USD" + }, + "listing": { + "id": "LST-760-660-960" + } + } + }, + { + "id": "STR-7755-7115-2464", + "name": "Simple pricing stream", + "status": "active", + "description": "Some description", + "visibility": "published", + "samples": { + "input": { + "id": "MFL-9059-7665-2037", + "name": "/public/v1/media/folders/streams_samples/STR-7755-7115-2464/files/MFL-9059-7665-2037/for_test.xlsx" + }, + "output": { + "id": "MFL-7908-1914-3192", + "name": "/public/v1/media/folders/streams_samples/STR-7755-7115-2464/files/MFL-7908-1914-3192/STR-7755-7115-2464-out.xlsx" + } + }, + "owner": { + "id": "VA-050-000", + "name": "Vendor account 00" + }, + "type": "pricing", + "context": { + "account": { + "id": "PA-050-101", + "name": "Provider account 01" + }, + "product": { + "id": "PRD-054-258-626", + "name": "Product P03", + "icon": "/media/VA-050-000/PRD-054-258-626/media/PRD-054-258-626-logo.png" + }, + "marketplace": { + "id": "MP-05011", + "name": "Marketplace M11", + "icon": "/media/PA-050-101/marketplaces/MP-05011/icon.png", + "currency": "USD" + }, + "listing": { + "id": "LST-760-660-960" + } + } + } +] \ No newline at end of file diff --git a/tests/fixtures/commerce/stream_retrieve_response.json b/tests/fixtures/commerce/stream_retrieve_response.json new file mode 100644 index 00000000..249f5c13 --- /dev/null +++ b/tests/fixtures/commerce/stream_retrieve_response.json @@ -0,0 +1,74 @@ +[{ + "id": "STR-7755-7115-2464", + "name": "Simple pricing stream", + "status": "active", + "description": "Some description", + "sources": [], + "visibility": "published", + "samples": { + "input": { + "id": "MFL-9059-7665-2037", + "name": "/public/v1/media/folders/streams_samples/STR-7755-7115-2464/files/MFL-9059-7665-2037/for_test.xlsx?session=f4dccfe4-7c86-4e16-b990-b68c6f9d40ce" + }, + "output": { + "id": "MFL-7908-1914-3192", + "name": "/public/v1/media/folders/streams_samples/STR-7755-7115-2464/files/MFL-7908-1914-3192/STR-7755-7115-2464-out.xlsx?session=f4dccfe4-7c86-4e16-b990-b68c6f9d40ce" + } + }, + "owner": { + "id": "VA-050-000", + "name": "Vendor account 00" + }, + "type": "pricing", + "events": { + "created": { + "at": "2023-07-26T13:47:56+00:00", + "by": { + "id": "UR-050-000-003", + "name": "Gerardu" + } + }, + "updated": { + "at": "2023-07-31T09:02:45+00:00", + "by": { + "id": "UR-050-000-003", + "name": "Gerardu" + } + }, + "activated": { + "at": "2023-07-31T09:02:45+00:00", + "by": { + "id": "UR-050-000-003", + "name": "Gerardu" + } + } + }, + "validation": { + "id": "BAT-6217-6818-2034", + "name": "Stream STR-7755-7115-2464 configuration test", + "status": "published" + }, + "context": { + "account": { + "id": "PA-050-101", + "name": "Provider account 01" + }, + "product": { + "id": "PRD-054-258-626", + "name": "Product P03", + "icon": "/media/VA-050-000/PRD-054-258-626/media/PRD-054-258-626-logo.png" + }, + "marketplace": { + "id": "MP-05011", + "name": "Marketplace M11 for galonso", + "icon": "/media/PA-050-101/marketplaces/MP-05011/icon.png", + "currency": "USD" + }, + "pricelist": { + "id": "PL-123" + }, + "listing": { + "id": "LST-760-660-960" + } + } +}] diff --git a/tests/fixtures/commerce/transformations_response.json b/tests/fixtures/commerce/transformations_response.json new file mode 100644 index 00000000..9b87892a --- /dev/null +++ b/tests/fixtures/commerce/transformations_response.json @@ -0,0 +1,117 @@ +[ + { + "id": "STRA-872-286-830-414-001", + "function": { + "id": "TRF-169-730-052", + "name": "Copy Column(s)", + "status": "active", + "icon": "/files/media/public/eaas_icons/SRVC-2374-4386/43b90a3205f5eb7d4b06.png" + }, + "description": "Some description", + "overview": "id → iddos", + "columns": { + "input": [ + { + "id": "SCOL-7755-7115-2464-001", + "name": "Customer Name", + "type": "string", + "position": 10000, + "nullable": true, + "required": false, + "output": true + }, + { + "id": "SCOL-7755-7115-2464-002", + "name": "Subscription ID", + "type": "string", + "position": 20000, + "nullable": true, + "required": false, + "output": true, + "description": "Subscription ID column" + } + ], + "output": [ + { + "id": "SCOL-8722-8683-0414-003", + "name": "iddos", + "type": "string", + "description": "", + "position": 30000, + "nullable": true, + "required": false + }, + { + "id": "SCOL-8722-8683-0414-004", + "name": "iddos", + "type": "string", + "description": "", + "position": 30000, + "nullable": true, + "required": false + } + ] + }, + "settings": [ + { + "to": "iddos", + "from": "id", + "other": { + "verybigfield": "sasdasdasdasdasdasdasdasd", + "another1": "asdasdasdasdasdasd", + "another2": "asdasdasdasdasdasd", + "another3": "asdasdasdasdasdasd", + "another4": "asdasdasdasdasdasdddd" + }, + "another": { + "another1": "asdasdasdasdasdasd", + "another2": "asdasdasdasdasdasd", + "another3": "asdasdasdasdasdasd", + "another4": "asdasdasdasdasdasdddd" + } + } + ], + "position": 10000 + }, + { + "id": "STRA-872-286-830-415-001", + "function": { + "id": "TRF-169-730-053", + "name": "Split Column(s)", + "status": "active", + "icon": "/files/media/public/eaas_icons/SRVC-2374-4386/43b90a3205f5eb7d4b06.png" + }, + "overview": "id → iddos", + "columns": { + "input": [ + { + "id": "SCOL-7755-7115-2464-011", + "name": "datetime", + "type": "datetime", + "position": 110000, + "nullable": true, + "required": false, + "output": true + } + ], + "output": [ + { + "id": "SCOL-8722-8683-0415-003", + "name": "iddos", + "type": "string", + "description": "", + "position": 30000, + "nullable": true, + "required": false + } + ] + }, + "settings": [ + { + "to": "iddos", + "from": "id" + } + ], + "position": 10001 + } +] \ No newline at end of file diff --git a/tests/plugins/commerce/__init__.py b/tests/plugins/commerce/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/plugins/commerce/test_commands.py b/tests/plugins/commerce/test_commands.py new file mode 100644 index 00000000..57d3e542 --- /dev/null +++ b/tests/plugins/commerce/test_commands.py @@ -0,0 +1,288 @@ +import json +import tempfile +import os + +import pytest +from click.testing import CliRunner + + +def test_list_streams(mocker, ccli, mocked_responses, config_mocker): + mocked_table = mocker.patch('connect.cli.plugins.commerce.utils.console.table') + with open('./tests/fixtures/commerce/billing_streams_response.json') as bsresponse: + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/billing/streams?select(context,sources)&limit=25&offset=0', + json=json.load(bsresponse), + ) + with open('./tests/fixtures/commerce/pricing_streams_response.json') as psresponse: + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/pricing/streams?select(context,sources)&limit=25&offset=0', + json=json.load(psresponse), + ) + + runner = CliRunner() + result = runner.invoke( + ccli, + ['commerce', 'stream', 'list'], + ) + assert result.exit_code == 0 + + mocked_table.assert_called_with( + columns=[ + 'ID', + 'Business scope', + 'Name', + 'Type', + 'Category', + 'Status', + 'Visibility', + ], + rows=[ + ( + 'STR-6250-2064-0382', + 'Billing', + 'My simple billing from source', + 'Computed', + 'Inbound', + 'Configuring', + 'Private', + ), + ( + 'STR-7755-0103-9379', + 'Billing', + 'My simple billing stream', + 'Simple', + 'Outbound', + 'Active', + 'Private', + ), + ( + 'STR-8722-8683-0414', + 'Billing', + 'Simple Billing stream', + 'Simple', + 'Inbound', + 'Active', + 'Published', + ), + ( + 'STR-4047-7247-4594', + 'Pricing', + 'My simple pricing stream', + 'Simple', + 'Outbound', + 'Configuring', + 'Private', + ), + ( + 'STR-5066-5885-2990', + 'Pricing', + 'My computed pricing from pricing stream', + 'Computed', + 'Inbound', + 'Active', + 'Private', + ), + ( + 'STR-7755-7115-2464', + 'Pricing', + 'Simple pricing stream', + 'Simple', + 'Inbound', + 'Active', + 'Published', + ), + ], + ) + + +def test_list_streams_empty(mocker, ccli, mocked_responses, config_mocker): + mocked_secho = mocker.patch('connect.cli.plugins.commerce.utils.console.secho') + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/billing/streams?select(context,sources)&limit=25&offset=0', + json=[], + ) + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/pricing/streams?select(context,sources)&limit=25&offset=0', + json=[], + ) + + runner = CliRunner() + result = runner.invoke( + ccli, + ['commerce', 'stream', 'list'], + ) + assert result.exit_code == 0 + + mocked_secho.assert_called_with( + f'Results not found for the current account VA-000.', + fg='yellow', + ) + + +def test_export_stream_not_found(mocker, ccli, mocked_responses, config_mocker): + mocked_secho = mocker.patch('connect.cli.plugins.commerce.utils.console.secho') + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/billing/streams?eq(id,STR-123)&limit=0&offset=0', + json=[], + headers={ + 'Content-Range': 'items 0-0/0', + }, + ) + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/pricing/streams?eq(id,STR-123)&limit=0&offset=0', + json=[], + headers={ + 'Content-Range': 'items 0-0/0', + }, + ) + + runner = CliRunner() + result = runner.invoke( + ccli, + [ + 'commerce', + 'stream', + 'export', + 'STR-123', + ], + ) + + assert result.exit_code == 0 + mocked_secho.assert_called_with( + f'Stream STR-123 not found for the current account VA-000.', + fg='red', + ) + + +@pytest.mark.parametrize('create_sample_input', (True, False)) +@pytest.mark.parametrize('create_attachments', (True, False)) +def test_export_stream_pricing( + mocker, + ccli, + mocked_responses, + config_mocker, + create_attachments, + create_sample_input, +): + mocker.patch( + 'connect.cli.plugins.commerce.utils.guess_if_billing_or_pricing_stream', + return_value='pricing', + ) + with open('./tests/fixtures/commerce/stream_retrieve_response.json') as content: + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/pricing/streams?eq(id,STR-7755-7115-2464)&select(context,samples,sources)&limit=1&offset=0', + json=json.load(content), + ) + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/media/folders/streams_samples/STR-7755-7115-2464/files/MFL-9059-7665-2037', + body=b'somecontent', + ) + with open('./tests/fixtures/commerce/columns_response.json') as content: + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/pricing/streams/STR-7755-7115-2464/columns?limit=100&offset=0', + json=json.load(content), + ) + with open('./tests/fixtures/commerce/transformations_response.json') as content: + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/pricing/streams/STR-7755-7115-2464/transformations?limit=100&offset=0', + json=json.load(content), + ) + with open('./tests/fixtures/commerce/attachments_response.json') as content: + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/media/folders/streams_attachments/STR-7755-7115-2464/files?limit=100&offset=0', + json=json.load(content), + ) + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/media/folders/streams_attachments/STR-7755-7115-2464/files/MFL-2481-0572-9001', + body=b'somecontent', + ) + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/media/folders/streams_attachments/STR-7755-7115-2464/files/MFL-8784-6884-8192', + body=b'somecontent', + ) + + runner = CliRunner() + with tempfile.TemporaryDirectory() as tmpdir: + mocker.patch('os.getcwd', return_value=tmpdir) + if create_attachments: + os.mkdir(os.path.join(tmpdir, 'attachments')) + if create_sample_input: + os.makedirs(os.path.join(tmpdir, 'sample', 'input')) + mocked_output = mocker.patch( + 'connect.cli.plugins.commerce.utils.validate_output_options', + return_value=os.path.join(tmpdir, 'STR-7755-7115-2464.xlsx'), + ) + result = runner.invoke( + ccli, + [ + 'commerce', + 'stream', + 'export', + 'STR-7755-7115-2464', + ], + ) + mocked_output.assert_called_with(None, None, default_dir_name='STR-7755-7115-2464') + + assert result.exit_code == 0 + + +def test_export_stream_transformations_client_error(mocker, ccli, mocked_responses, config_mocker): + mocker.patch('connect.cli.plugins.commerce.utils.console.secho') + mocker.patch( + 'connect.cli.plugins.commerce.utils.guess_if_billing_or_pricing_stream', + return_value='pricing', + ) + with open('./tests/fixtures/commerce/stream_retrieve_response.json') as content: + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/pricing/streams?eq(id,STR-7755-7115-2464)&select(context,samples,sources)&limit=1&offset=0', + json=json.load(content), + ) + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/media/folders/streams_samples/STR-7755-7115-2464/files/MFL-9059-7665-2037', + body=b'somecontent', + ) + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/pricing/streams/STR-7755-7115-2464/columns?limit=100&offset=0', + json=[], + ) + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/pricing/streams/STR-7755-7115-2464/transformations?limit=100&offset=0', + status=404, + ) + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/media/folders/streams_attachments/STR-7755-7115-2464/files?limit=100&offset=0', + json=[], + ) + + runner = CliRunner() + with tempfile.TemporaryDirectory() as tmpdir: + mocker.patch('os.getcwd', return_value=tmpdir) + result = runner.invoke( + ccli, + [ + 'commerce', + 'stream', + 'export', + 'STR-7755-7115-2464', + ], + ) + + assert result.exit_code == 0 diff --git a/tests/plugins/commerce/test_utils.py b/tests/plugins/commerce/test_utils.py new file mode 100644 index 00000000..43b755aa --- /dev/null +++ b/tests/plugins/commerce/test_utils.py @@ -0,0 +1,90 @@ +import os +import tempfile + +import pytest +from click import ClickException +from openpyxl import Workbook + +from connect.cli.plugins.commerce.utils import ( + fill_and_download_attachments, + guess_if_billing_or_pricing_stream, +) + + +def test_guess_if_billing_or_pricing_billing(mocked_responses, client): + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/billing/streams?eq(id,STR-123)&limit=0&offset=0', + json=[], + headers={ + 'Content-Range': 'items 0-1/1', + }, + ) + assert guess_if_billing_or_pricing_stream(client, 'STR-123') == 'billing' + + +def test_guess_if_billing_or_pricing_pricing(mocked_responses, client): + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/billing/streams?eq(id,STR-567)&limit=0&offset=0', + json=[], + headers={ + 'Content-Range': 'items 0-0/0', + }, + ) + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/pricing/streams?eq(id,STR-123)&limit=0&offset=0', + json=[], + headers={ + 'Content-Range': 'items 0-1/1', + }, + ) + assert guess_if_billing_or_pricing_stream(client, 'STR-123') == 'pricing' + + +def test_guess_if_billing_or_pricing_none(mocked_responses, client): + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/billing/streams?eq(id,STR-567)&limit=0&offset=0', + json=[], + headers={ + 'Content-Range': 'items 0-0/0', + }, + ) + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/pricing/streams?eq(id,STR-123)&limit=0&offset=0', + json=[], + headers={ + 'Content-Range': 'items 0-0/0', + }, + ) + assert guess_if_billing_or_pricing_stream(client, 'STR-123') is None + + +def test_fill_attachments_download_fails(mocked_responses, mocker, client): + mocked_responses.add( + method='GET', + url='https://localhost/public/v1/media/folders/ftype/fname/files/id', + status=500, + ) + with tempfile.TemporaryDirectory() as tmpdir: + wb = Workbook() + ws = wb.create_sheet('Attachments') + mocked_progress = mocker.MagicMock() + with pytest.raises(ClickException) as exc: + fill_and_download_attachments( + ws, + [ + { + 'id': 'id', + 'name': 'name', + 'folder': {'type': 'ftype', 'name': 'fname'}, + }, + ], + client, + os.path.join(tmpdir, 'media'), + mocked_progress, + ) + assert exec.value == 'Error obtaining file name'