From e88f9bd8aebf1cea68ba1b30280199dfd611431b Mon Sep 17 00:00:00 2001 From: svariant Date: Thu, 7 Dec 2023 12:41:43 +0100 Subject: [PATCH 01/38] [PRDP-242] Updated int test env --- integration-test/src/config/.env.dev | 9 ++++++++- integration-test/src/config/.env.local | 11 ++++++++++- integration-test/src/config/.env.uat | 9 ++++++++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/integration-test/src/config/.env.dev b/integration-test/src/config/.env.dev index a446cb8..84f5772 100644 --- a/integration-test/src/config/.env.dev +++ b/integration-test/src/config/.env.dev @@ -5,5 +5,12 @@ BIZ_EVENT_COSMOS_DB_CONTAINER_NAME=biz-events RECEIPTS_COSMOS_CONN_STRING= RECEIPT_COSMOS_DB_NAME=db RECEIPT_COSMOS_DB_CONTAINER_NAME=receipts +RECEIPT_ERROR_COSMOS_DB_CONTAINER_NAME=receipts-message-errors -HELPDESK_URL=https://api.dev.platform.pagopa.it/receipts/helpdesk/v1/recoverFailed \ No newline at end of file +BLOB_STORAGE_ACCOUNT_KEY= +BLOB_STORAGE_ACCOUNT_NAME=pagopadweureceiptsfnsa +BLOB_STORAGE_CONTAINER_NAME=pagopa-d-weu-receipts-azure-blob-receipt-st-attach + +ENVIRONMENT=dev + +HELPDESK_URL=https://api.dev.platform.pagopa.it/receipts/helpdesk/v1/ \ No newline at end of file diff --git a/integration-test/src/config/.env.local b/integration-test/src/config/.env.local index f725a6f..b8618c8 100644 --- a/integration-test/src/config/.env.local +++ b/integration-test/src/config/.env.local @@ -4,4 +4,13 @@ BIZ_EVENT_COSMOS_DB_CONTAINER_NAME=biz-events RECEIPTS_COSMOS_CONN_STRING= RECEIPT_COSMOS_DB_NAME=db -RECEIPT_COSMOS_DB_CONTAINER_NAME=receipts \ No newline at end of file +RECEIPT_COSMOS_DB_CONTAINER_NAME=receipts +RECEIPT_ERROR_COSMOS_DB_CONTAINER_NAME=receipts-message-errors + +BLOB_STORAGE_ACCOUNT_KEY= +BLOB_STORAGE_ACCOUNT_NAME=pagopadweureceiptsfnsa +BLOB_STORAGE_CONTAINER_NAME=pagopa-d-weu-receipts-azure-blob-receipt-st-attach + +ENVIRONMENT=local + +HELPDESK_URL=http://localhost:53488/ \ No newline at end of file diff --git a/integration-test/src/config/.env.uat b/integration-test/src/config/.env.uat index 078e67f..e568853 100644 --- a/integration-test/src/config/.env.uat +++ b/integration-test/src/config/.env.uat @@ -5,5 +5,12 @@ BIZ_EVENT_COSMOS_DB_CONTAINER_NAME=biz-events RECEIPTS_COSMOS_CONN_STRING= RECEIPT_COSMOS_DB_NAME=db RECEIPT_COSMOS_DB_CONTAINER_NAME=receipts +RECEIPT_ERROR_COSMOS_DB_CONTAINER_NAME=receipts-message-errors -HELPDESK_URL=https://api.uat.platform.pagopa.it/receipts/helpdesk/v1/recoverFailed \ No newline at end of file +BLOB_STORAGE_ACCOUNT_KEY= +BLOB_STORAGE_ACCOUNT_NAME=pagopauweureceiptsfnsa +BLOB_STORAGE_CONTAINER_NAME=pagopa-u-weu-receipts-azure-blob-receipt-st-attach + +ENVIRONMENT=uat + +HELPDESK_URL=https://api.uat.platform.pagopa.it/receipts/helpdesk/v1/ \ No newline at end of file From 6d703d1b337d5c3507dd3c503ec44f40ff915b1a Mon Sep 17 00:00:00 2001 From: svariant Date: Thu, 7 Dec 2023 12:42:23 +0100 Subject: [PATCH 02/38] [PRDP-242] Update git ignore --- .gitignore | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d6fe466..7be9d64 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,10 @@ hs_err_pid* local.settings.json bin/ obj/ -**/.identity \ No newline at end of file +**/.identity +.azure/ + +package-lock.json +yarn.lock +.env +node_modules/ \ No newline at end of file From ddefadae800524911de9f4eea4d7c6d42a5fe3ed Mon Sep 17 00:00:00 2001 From: svariant Date: Thu, 7 Dec 2023 12:42:45 +0100 Subject: [PATCH 03/38] [PRDP-242] Defined first integration steps --- .../src/features/receipt_pdf_helpdesk.feature | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/integration-test/src/features/receipt_pdf_helpdesk.feature b/integration-test/src/features/receipt_pdf_helpdesk.feature index 2faa5d6..d2c672d 100644 --- a/integration-test/src/features/receipt_pdf_helpdesk.feature +++ b/integration-test/src/features/receipt_pdf_helpdesk.feature @@ -1,24 +1,26 @@ Feature: All about payment events to recover managed by Azure functions receipt-pdf-helpdesk - Scenario: a biz event stored on biz-events datastore is stored into receipts datastore - Given a random biz event with id "receipt-helpdesk-int-test-id-1" stored on biz-events datastore with status DONE - When biz event has been properly stored into receipt datastore after 10000 ms with eventId "receipt-helpdesk-int-test-id-1" - Then the receipts datastore returns the receipt + Scenario: getReceipt API return receipt stored on datastore + Given a receipt with eventId "receipt-helpdesk-int-test-id-1" stored into receipt datastore + When getReceipt API is called with eventId "receipt-helpdesk-int-test-id-1" + Then the api response has a 200 Http status And the receipt has eventId "receipt-helpdesk-int-test-id-1" - Given a random receipt with id "receipt-helpdesk-int-test-id-1" stored with status FAILED - When HTTP recovery request is called - Then response has a 200 Http status - And the receipt has not the status "helpdesk" after 10000 ms + Scenario: getReceiptByOrganizationFiscalCodeAndIUV API return receipt stored on datastore + Given a receipt with eventId "receipt-helpdesk-int-test-id-2" stored into receipt datastore + And a biz event with id "receipt-helpdesk-int-test-id-2" stored on biz-events datastore with status "DONE" + When getReceiptByOrganizationFiscalCodeAndIUV API is called with organizationFiscalCode "intTestOrgCode" and IUV "intTestIuv" + Then the api response has a 200 Http status + And the receipt has eventId "receipt-helpdesk-int-test-id-2" - Given a random receipt with id "receipt-helpdesk-int-test-id-1" stored with status FAILED - When HTTP recovery request is called without eventId - Then response has a 200 Http status - And the receipt has not the status "FAILED" after 10000 ms + Scenario: getReceiptError API return receipt-error stored on datastore + Given a receipt-error with bizEventId "receipt-helpdesk-int-test-id-3" stored into receipt-error datastore + When getReceiptError API is called with bizEventId "receipt-helpdesk-int-test-id-3" + Then the api response has a 200 Http status + And the receipt-error has bizEventId "receipt-helpdesk-int-test-id-3" + And the receipt-error payload has bizEvent decrypted with eventId "receipt-helpdesk-int-test-id-3" - Scenario: a receipt stored on datastore with wrong or missing attachment requires regeneration - Given a receipt with id "receipt-helpdesk-int-test-id-5" stored into receipt datastore - And a biz-event id "receipt-helpdesk-int-test-id-5" stored into biz-event datastore - When HTTP regenerate request is called - Then response has a 200 Http status - And the receipt has the attachment \ No newline at end of file + Scenario: getReceiptPdf API return receipt pdf store on blob storage + Given a receipt pdf with filename "int-test-helpdesk-receipt.pdf" stored into blob storage + When getReceiptPdf API is called with filename "int-test-helpdesk-receipt.pdf" + Then the api response has a 200 Http status \ No newline at end of file From e984f118eeec24e8809e400360dc300d1e8ffd93 Mon Sep 17 00:00:00 2001 From: svariant Date: Thu, 7 Dec 2023 12:44:03 +0100 Subject: [PATCH 04/38] [PRDP-242] Implemented api & blob client and updated other clients --- .../step_definitions/api_helpdesk_client.js | 68 ++++++++++++++++ .../biz_events_datastore_client.js | 4 +- .../step_definitions/blob_storage_client.js | 48 +++++++++++ .../receipts_datastore_client.js | 80 +++++++++++++++---- 4 files changed, 182 insertions(+), 18 deletions(-) create mode 100644 integration-test/src/step_definitions/api_helpdesk_client.js create mode 100644 integration-test/src/step_definitions/blob_storage_client.js diff --git a/integration-test/src/step_definitions/api_helpdesk_client.js b/integration-test/src/step_definitions/api_helpdesk_client.js new file mode 100644 index 0000000..f1683cf --- /dev/null +++ b/integration-test/src/step_definitions/api_helpdesk_client.js @@ -0,0 +1,68 @@ +const axios = require("axios"); + +const helpdesk_url = process.env.HELPDESK_URL; + +axios.defaults.headers.common['Ocp-Apim-Subscription-Key'] = process.env.SUBKEY || ""; // for all requests +if (process.env.canary) { + axios.defaults.headers.common['X-CANARY'] = 'canary' // for all requests +} + +async function getReceipt(id) { + let endpoint = process.env.GET_RECEIPT_ENDPOINT || "receipts/{event-id}"; + endpoint = endpoint.replace("{event-id}", id); + + return await axios.get(helpdesk_url + endpoint) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function getReceiptByOrganizationFiscalCodeAndIUV(orgCode, iuv) { + let endpoint = process.env.GET_RECEIPT_BY_ORGCODE_AND_IUV_ENDPOINT || "receipts/organizations/{organization-fiscal-code}/iuvs/{iuv}"; + endpoint = endpoint.replace("{organization-fiscal-code}", orgCode); + endpoint = endpoint.replace("{iuv}", iuv); + + return await axios.get(helpdesk_url + endpoint) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function getReceiptError(id) { + let endpoint = process.env.GET_RECEIPT_ERROR_ENDPOINT || "errors-toreview/{bizvent-id}"; + endpoint = endpoint.replace("{bizvent-id}", id); + + return await axios.get(helpdesk_url + endpoint) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function getReceiptPdf(fileName) { + let endpoint = process.env.GET_RECEIPT_PDF_ENDPOINT || "pdf-receipts/{file-name}"; + endpoint = endpoint.replace("{file-name}", fileName); + + return await axios.get(helpdesk_url + endpoint) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +module.exports = { + getReceipt, + getReceiptByOrganizationFiscalCodeAndIUV, + getReceiptError, + getReceiptPdf +} \ No newline at end of file diff --git a/integration-test/src/step_definitions/biz_events_datastore_client.js b/integration-test/src/step_definitions/biz_events_datastore_client.js index b719488..f6bc909 100644 --- a/integration-test/src/step_definitions/biz_events_datastore_client.js +++ b/integration-test/src/step_definitions/biz_events_datastore_client.js @@ -17,8 +17,8 @@ async function getDocumentByIdFromBizEventsDatastore(id) { .fetchAll(); } -async function createDocumentInBizEventsDatastore(id) { - let event = createEvent(id); +async function createDocumentInBizEventsDatastore(id, status) { + let event = createEvent(id, status); try { return await container.items.create(event); } catch (err) { diff --git a/integration-test/src/step_definitions/blob_storage_client.js b/integration-test/src/step_definitions/blob_storage_client.js new file mode 100644 index 0000000..cd1dfdd --- /dev/null +++ b/integration-test/src/step_definitions/blob_storage_client.js @@ -0,0 +1,48 @@ +const { BlobServiceClient, StorageSharedKeyCredential } = require('@azure/storage-blob'); + +const blobStorageContainerName = process.env.BLOB_STORAGE_CONTAINER_NAME; + +// Azure Storage resource name +const accountName = process.env.BLOB_STORAGE_ACCOUNT_NAME; +if (!accountName) throw Error("Azure Storage accountName not found"); + +// Azure Storage resource key +const accountKey = process.env.BLOB_STORAGE_ACCOUNT_KEY; +if (!accountKey) throw Error("Azure Storage accountKey not found"); + +// Create credential +const sharedKeyCredential = new StorageSharedKeyCredential( + accountName, + accountKey +); + +const blobServiceClient = new BlobServiceClient(`https://${accountName}.blob.core.windows.net`, sharedKeyCredential); +const containerClient = blobServiceClient.getContainerClient(blobStorageContainerName); + +async function uploadBlobFromLocalPath(fileName, localFilePath) { + const blobClient = containerClient.getBlockBlobClient(fileName); + + try { + return await blobClient.uploadFile(localFilePath); + } catch (err) { + return { status: 500 } + } +} + +async function deleteBlob(blobName) { + // include: Delete the base blob and all of its snapshots. + // only: Delete only the blob's snapshots and not the blob itself. + const options = { + deleteSnapshots: 'include' // or 'only' + } + + // Create blob client from container client + const blockBlobClient = containerClient.getBlockBlobClient(blobName); + + await blockBlobClient.deleteIfExists(options); +} + +module.exports = { + uploadBlobFromLocalPath, + deleteBlob +} \ No newline at end of file diff --git a/integration-test/src/step_definitions/receipts_datastore_client.js b/integration-test/src/step_definitions/receipts_datastore_client.js index 314842a..1db65df 100644 --- a/integration-test/src/step_definitions/receipts_datastore_client.js +++ b/integration-test/src/step_definitions/receipts_datastore_client.js @@ -1,14 +1,26 @@ const { CosmosClient } = require("@azure/cosmos"); -const { createReceipt } = require("./common"); +const { createReceipt, createReceiptError } = require("./common"); -const cosmos_db_conn_string = process.env.RECEIPTS_COSMOS_CONN_STRING || ""; -const databaseId = process.env.RECEIPT_COSMOS_DB_NAME; -const receiptContainerId = process.env.RECEIPT_COSMOS_DB_CONTAINER_NAME; +const cosmos_db_conn_string = process.env.RECEIPTS_COSMOS_CONN_STRING || ""; +const databaseId = process.env.RECEIPT_COSMOS_DB_NAME; +const receiptContainerId = process.env.RECEIPT_COSMOS_DB_CONTAINER_NAME; +const receiptErrorContainerId = process.env.RECEIPT_ERROR_COSMOS_DB_CONTAINER_NAME; const client = new CosmosClient(cosmos_db_conn_string); const receiptContainer = client.database(databaseId).container(receiptContainerId); +const receiptErrorContainer = client.database(databaseId).container(receiptErrorContainerId); -async function getDocumentByIdFromReceiptsDatastore(id) { +//RECEIPT +async function createDocumentInReceiptsDatastore(id, status) { + let receipt = createReceipt(id, status); + try { + return await receiptContainer.items.create(receipt); + } catch (err) { + console.log(err); + } +} + +async function getDocumentByIdFromReceiptsDatastoreByEventId(id) { return await receiptContainer.items .query({ query: "SELECT * from c WHERE c.eventId=@eventId", @@ -17,17 +29,45 @@ async function getDocumentByIdFromReceiptsDatastore(id) { .fetchNext(); } -async function deleteDocumentFromReceiptsDatastoreByEventId(eventId){ - let documents = await getDocumentByIdFromReceiptsDatastore(eventId); +async function deleteMultipleDocumentsFromReceiptsDatastoreByEventId(eventId) { + let documents = await getDocumentByIdFromReceiptsDatastoreByEventId(eventId); + + documents?.resources?.forEach(el => { + deleteDocumentFromReceiptsDatastore(el.id); + }) +} + +async function deleteDocumentFromReceiptsDatastore(id) { + try { + return await receiptContainer.item(id, id).delete(); + } catch (error) { + if (error.code !== 404) { + console.log(error) + } + } +} + +//RECEIPT-ERROR +async function createDocumentInReceiptErrorDatastore(id, status) { + let receipt = createReceiptError(id, status); + try { + return await receiptErrorContainer.items.create(receipt); + } catch (err) { + console.log(err); + } +} + +async function deleteMultipleDocumentFromReceiptErrorDatastoreByEventId(id) { + let documents = await getDocumentByIdFromReceiptsDatastoreByEventId(id); documents?.resources?.forEach(el => { - deleteDocumentFromReceiptsDatastore(el.id, eventId); + deleteDocumentFromReceiptErrorDatastore(el.id); }) } -async function deleteDocumentFromReceiptsDatastore(id, partitionKey) { +async function deleteDocumentFromReceiptErrorDatastore(id) { try { - return await receiptContainer.item(id, partitionKey).delete(); + return await receiptErrorContainer.item(id, id).delete(); } catch (error) { if (error.code !== 404) { console.log(error) @@ -35,15 +75,15 @@ async function deleteDocumentFromReceiptsDatastore(id, partitionKey) { } } -async function updateReceiptToFailed(id, partitionKey) { +async function updateReceiptToFailed(id) { const operations = - [ - { op: 'replace', path: '/status', value: 'FAILED' } - ]; + [ + { op: 'replace', path: '/status', value: 'FAILED' } + ]; try { - return await receiptContainer.item(id, partitionKey).patch(operations); + return await receiptContainer.item(id, id).patch(operations); } catch (error) { if (error.code !== 404) { console.log(error) @@ -52,5 +92,13 @@ async function updateReceiptToFailed(id, partitionKey) { } module.exports = { - getDocumentByIdFromReceiptsDatastore, deleteDocumentFromReceiptsDatastoreByEventId, deleteDocumentFromReceiptsDatastore, updateReceiptToFailed + createDocumentInReceiptsDatastore, + getDocumentByIdFromReceiptsDatastoreByEventId, + deleteMultipleDocumentsFromReceiptsDatastoreByEventId, + deleteDocumentFromReceiptsDatastore, + updateReceiptToFailed, + + deleteDocumentFromReceiptErrorDatastore, + deleteMultipleDocumentFromReceiptErrorDatastoreByEventId, + createDocumentInReceiptErrorDatastore } \ No newline at end of file From e88ae1569844b53f4c08368d1de1e44c05e0d5d1 Mon Sep 17 00:00:00 2001 From: svariant Date: Thu, 7 Dec 2023 12:44:33 +0100 Subject: [PATCH 05/38] [PRDP-242] Defined test methods --- .../src/step_definitions/common.js | 106 ++++-------- .../receipt_pdf_helpdesk_step.js | 160 ++++++++++-------- 2 files changed, 120 insertions(+), 146 deletions(-) diff --git a/integration-test/src/step_definitions/common.js b/integration-test/src/step_definitions/common.js index 77913dc..b208035 100644 --- a/integration-test/src/step_definitions/common.js +++ b/integration-test/src/step_definitions/common.js @@ -1,26 +1,10 @@ -const axios = require("axios"); - const TOKENIZED_FISCAL_CODE = "cd07268c-73e8-4df4-8305-a35085e32eff"; -const helpdesk_url = process.env.HELPDESK_URL; -const recover_failed_endpoint = process.env.RECOVER_FAILED_ENDPOINT || "/recoverFailed"; - -axios.defaults.headers.common['Ocp-Apim-Subscription-Key'] = process.env.SUBKEY || ""; // for all requests -if (process.env.canary) { - axios.defaults.headers.common['X-CANARY'] = 'canary' // for all requests -} - function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } -function createEventForDatastore(id) { - let json_event = createEvent(id); - json_event.eventStatus = "TEST" - return json_event; -} - -function createEvent(id) { +function createEvent(id, status) { let json_event = { "id": id, "version": "2", @@ -36,10 +20,10 @@ function createEvent(id) { "debtorPosition": { "modelType": "2", "noticeNumber": "310391366991197059", - "iuv": "10391366991197059" + "iuv": "intTestIuv" }, "creditor": { - "idPA": "66666666666", + "idPA": "intTestOrgCode", "idBrokerPA": "66666666666", "idStation": "66666666666_08", "companyName": "PA paolo", @@ -130,31 +114,13 @@ function createEvent(id) { "diagnostic-id": "00-f70ef3167cffad76c6657a67a33ee0d2-61d794a75df0b43b-01", "serviceIdentifier": "NDP002SIT" }, - "eventStatus": "DONE", + "eventStatus": status || "DONE", "eventRetryEnrichmentCount": 0 } return json_event } -function createReceipt(id, fiscalCode, pdfName) { - let receipt = - { - "eventId": id, - "eventData": { - "debtorFiscalCode": fiscalCode, - "payerFiscalCode": fiscalCode - }, - "status": "IO_NOTIFIED", - "mdAttach": { - "name": pdfName, - "url": pdfName - }, - "id": id - } - return receipt -} - -function createReceipt(id) { +function createReceipt(id, status, pdfName) { let receipt = { "eventId": id, @@ -169,52 +135,38 @@ function createReceipt(id) { } ] }, - "status": "INSERTED", + "status": status || "INSERTED", "numRetry": 0, - "id": id, - "_rid": "Z9AJAMdamqNjAAAAAAAAAA==", - "_self": "dbs/Z9AJAA==/colls/Z9AJAMdamqM=/docs/Z9AJAMdamqNjAAAAAAAAAA==/", - "_etag": "\"08007a84-0000-0d00-0000-648b1e510000\"", - "_attachments": "attachments/", - "_ts": 1686838865 + "id": id + } + if (status === "NOTIFIED" || status === "GENERATED") { + receipt.mdAttach = { "name": pdfName, "url": pdfName }; } return receipt } -async function recoverFailedEvent(eventId) { - - var data = {} - if (eventId != null) { - data = JSON.stringify({ "eventId": eventId }); - } - - return await axios.put(helpdesk_url+recover_failed_endpoint, data, {}) - .then(res => { - return res; - }) - .catch(error => { - return error.response; - }); +const getTokenizedBizEvent = () => { + let environment = process.env.ENVIRONMENT || ""; + if (environment === "uat") { + return "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"; + } + return "AWL5bnBg68AfzYAVg4yQgEhXUeja1sPe95M8FI3pL+RHha0ZthwheWJRraeqEmu8tiW13pyjwVY6U9J2NnmJW9ouli+vWa3q8igmT1t1PIaRgcivLPumU+HKkCRRgrXXQbYpzb76yH9deGJfI4BX7AlBywGx0rDkY/80nI6vbSOFYmg8s175fQqmkLmpG+k39IJt558z88pOaWGp+Xsmn7wUivQdI+c2MYiYto9VhieGS/R3IX+EhOEQLm6NxlOKfC8auP3bf26sDnLIpLjXGG7xBG8tSe07Gx1m9ubNzFV2Kr1AGxhq8cht+cnprMfNfXG262aPaW3HxTVtQ3FpWz3NY4yMG7NtqU9+YycnXfL9OZrwYIV3R51hoh5TrdPYC/5arcRksdDvKvFvdn/2kGyDyl/t/Mg7DjlnyH6xlokNzxiMZGCbALp9Y9cEaBqsucYztwVbIuq1e0CB8CgaKi3eYrYje6sgq7DV0WjT2lyMmWKv5aMLNk9vhCEsSZpyXvUIafcyEVNKe8GiCMJjp2JLNvf0ELsHEoY81p4T+S6P9lkP6mKBsbQChVG5KFvL/ZrMpP1Lx6boVZMXnTnrO5/eYwlMhW/ldEfjsg8vm2t5FWc/aBHKnoxFGyRcHGB0Gexs36AXGDgDJzb03bcZm4e3PxwHndVB3maCMUR6uGQVZB6cEHWZ4qZo2Hqa+UpE7SplUTI+CftMK3FbqzG1OTI4uZ2Bt+hRHVqBTOJJJwA/S8JuuW5lNSPQD2bZJuSPZKujgMrYCt3HDnpimR+2QDge6YDPm2KK/CB4vNGJAkwaDLJV3U36hRgJa8+dyIdoc5SlrpdKSIeLzSeKSwiFfI06V9wioTeQ0gJV9bSafQ+NkhxyJxeH/AUb3TTV3AzYwuO/nmCk9kkrO75VH8c9ICxpnQ34TLrApG9BeXSdn5cMmqB3CPrCSjHMBKtiFiypO87XOgAE56anSP3yKJtYCCCMvoYiUTNMhS593dNYoYRYzsI+E7u2gW8WBgPQidqfWGAQXTrzkL7z2GVYtCtNPoAvJpaf0oenS3RunR72G8GMvX7fEiwaFNUAeDBQZkJSUq/vpEAASBav7vEnwF3xolBHsTpe39V92V2Bq5IQpwtw6ZUVBu5a43aRt43nkmxqZLmGaw71XOcoymDH1c/OYbEPHwSyR9jRw0L74lRAO+jN6e3/9cOTPp+6C4E09OwDUYeCKMi2bZrBbKuTf1BGE9AsSQddlqWCsnY0J04RL5E27ehx5yywiRblTL8W8z2whRJG8/OYz3x0JmbH2jNW3ST9IDUMZkN5qOwy/KbdULvmhuaTlVVMKQt19fNOTJHg5WwPCl9vbHFPbiE505CzdqOEmecy6KvZxy6NUuMDaToiqgCZKDXlzDFhOoWNcKKN1kHwyQCoKZRCVTiY5iPWbQQ4hOd858BgkDpUV60iN52PYkeGyl/OOMHYFjEENU/sbWZBhzxOHd++fHbuhWyIR5iTJmign+2nkEcReDT7jm8cvMVeo/92QETbgNfIvLyx9sW8fsD0mdr9VJpFzsvRNm6rpylKf2Fjad0wbHFGgdhcCJtb8BLEBvsrfKPWFpaxY6GDIfs+kwiIHw5dekQ8yyL94ngofVfnPHcgp9LtVSS1RUo/PnGfE/imJardA+hMG0yJusfwMtNDnuyQwiV8cfHpblTqTf0eVD/MLinKv7naK5Hcg9hpyDaBnopMsJs8NN6WWhY30xHHQaKjiaX9SSyzYAuzhkeIjUkMRJcKELS3DnY34pIadSLAlZG3WGPV1QaVPJLf7SziXAiRhER9Cf32zpwYbddYysP52qd+jmsK5X70Z4pFm0JIuYlQ784ri7V4gmS2IwY7+2JvsJCJz+9Rx7hSHuaQteq525xCDJPvNoDAKlvfBugvio58WHjWNeBauJ+MnmeeTf66xb35coZ1DrSYlr04n1O4Jd/5VrW2nUfXlZiaGHSVSlaQ6gJ6d6PYd/gb3V3xIFIbVNWNMng9aZ23gCZg9HVJctJN9DDjryzgDHtUJ29Fdxgn677401TapY8OaXqeacVZ6aCDk0vO806Yv/TJuoc/y2EzOpkK0T7FjbaBaIK7tzsb6EOdN5aLpBG7/xu6Jv/m7udxVyPQHCfJn5eKfxwzlzuceBQC8+NEvQHI9txSW1sdODW9kjJP0DoLQt0sclFoNBlzyhHLT1qB90CAdArFQvdzCfEvZXQj5Zyfw0/NqlUTKtsRxXwRsRYiwuXvo6P4kICzGqpgPgJGOFTdFp2vYLe+4VYfeRCdOn0hpFW23CIPoA8llMrF1+LSImBEIGEiElu4SLrp9qnhFqNfWCwzshxS7ukgRd1CqfjL9962AYLxja/+RheFKul3i/kLSy2+ZQgN8aJ6gccAAdrTzhWMvlj4eSytOgL8uBDuY0bxvmFEQ98pBBVReCFxsFYWsxITFFLcKAkV0z+2XFy8HCRU7EUra2QUM8QBfSPd4QHyalSTs7BPP/jntuIwe2YrCsh3D1LEOMy1o/CQK5O7Vh2Wwcx7jyATwQNCh9X2VKW+DyaZCuq7nON7aZKIbgBDYMyPZSnXDdjO+o1egCp8+KVUsLPwC3vYTTS8P+/atW8pDNDrwjh8ou/StAfB9JKf9UqBVbvZeqEB+rubLF0kWBOTnQ80/+eZrXz3GhmAx82tt+r/fNnu0n3N232LW3YSxZKP9YQ+UarQrR/vQLnuFXwUabsN/txY/0L83Ud30Jg0kOX0P5kACKkv/hnZfLi1SnQXS7s87WrviWwIAOHo2Z62jzLCGyW55JnWQgCCouOimN11F9iJJl+dU2I3P+YsI7ITAUuvMJYo4XcdFS3FAJ6Jo+Se8pZNcQukOzyOqVNs2Fw5JMQ9K3ak7ZN2iWGshycnYE3B43g/nNR/dRNguzOrOol0C7dgsMZ7sloqin/Mmop2yDB/+nMPyuAbif15mKhLkCcRYaYJ4W9+ZecUVzTeRoeUslaJvY+Zv9b/5H3SIKK9fvK1A5mI5fEXhZnE61/yZIqr8g8rpGiUs9HtXCDvBtkYyhic6BRL4b/CaYI2n03a4TK6J7ua5YNSE86epl0G30FX3mi4t0zDsY9aZko9rFi85DaqFD52dVC81XVim26AvusfB26lh501W3HyvaJ8mgvXtPoXR+kUYGEhAfnVbxacLCU1mvPv1WphQvI36v2IQAXW"; } - -async function regeneratePdf(eventId) { - - var data = {} - if (eventId != null) { - data = JSON.stringify({ "eventId": eventId }); - } - - return await axios.put(datastore_url, data, {}) - .then(res => { - return res; - }) - .catch(error => { - return error.response; - }); - +const TOKENIZED_BIZ_EVENT = getTokenizedBizEvent(); +function createReceiptError(id, status) { + return { + "id": id, + "bizEventId": id, + "messagePayload": TOKENIZED_BIZ_EVENT, + "messageError": "Unexpected error when decrypting the given string", + "status": status || "TO_REVIEW", + } } module.exports = { - createEvent, sleep, recoverFailedEvent, regeneratePdf + createEvent, + sleep, + createReceipt, + createReceiptError } \ No newline at end of file diff --git a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js index ccd3746..a316fe5 100644 --- a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js +++ b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js @@ -1,118 +1,140 @@ const assert = require('assert'); const { After, Given, When, Then, setDefaultTimeout } = require('@cucumber/cucumber'); -const { sleep, recoverFailedEvent, regeneratePdf } = require("./common"); +let fs = require('fs'); +const { sleep } = require("./common"); const { createDocumentInBizEventsDatastore, deleteDocumentFromBizEventsDatastore } = require("./biz_events_datastore_client"); -const { getDocumentByIdFromReceiptsDatastore, deleteDocumentFromReceiptsDatastoreByEventId, deleteDocumentFromReceiptsDatastore, updateReceiptToFailed } = require("./receipts_datastore_client"); +const { + getDocumentByIdFromReceiptsDatastoreByEventId, + deleteDocumentFromReceiptsDatastore, + createDocumentInReceiptsDatastore, + deleteMultipleDocumentsFromReceiptsDatastoreByEventId, + deleteMultipleDocumentFromReceiptErrorDatastoreByEventId, + createDocumentInReceiptErrorDatastore, + deleteDocumentFromReceiptErrorDatastore +} = require("./receipts_datastore_client"); +const { + getReceipt, + getReceiptByOrganizationFiscalCodeAndIUV, + getReceiptError, + getReceiptPdf +} = require("./api_helpdesk_client"); +const { uploadBlobFromLocalPath, deleteBlob } = require("./blob_storage_client"); // set timeout for Hooks function, it allows to wait for long task setDefaultTimeout(360 * 1000); // initialize variables -this.eventId = null; -this.responseToCheck = null; -this.response = null; -this.receiptId = null; -this.event = null; +let eventId = null; +let responseAPI = null; +let responseCosmos = null; +let receipt = null; +let receiptError = null; +let event = null; +let receiptPdfFileName = null; // After each Scenario After(async function () { // remove event - if (this.eventId != null) { - await deleteDocumentFromBizEventsDatastore(this.eventId); + if (eventId != null) { + await deleteDocumentFromBizEventsDatastore(eventId); + await deleteMultipleDocumentsFromReceiptsDatastoreByEventId(eventId); + await deleteMultipleDocumentFromReceiptErrorDatastoreByEventId(eventId); } - if (this.eventId != null && this.receiptId != null) { - await deleteDocumentFromReceiptsDatastore(this.receiptId, this.eventId); + if(receiptPdfFileName != null){ + await deleteBlob(receiptPdfFileName); + fs.unlinkSync(receiptPdfFileName); } - this.eventId = null; - this.responseToCheck = null; - this.receiptId = null; - this.event = null; }); -Given('a random biz event with id {string} stored on biz-events datastore with status DONE', async function (id) { - this.eventId = id; +//Given +Given('a biz event with id {string} stored on biz-events datastore with status {string}', async function (id, status) { + eventId = id; // prior cancellation to avoid dirty cases - await deleteDocumentFromBizEventsDatastore(this.eventId); - await deleteDocumentFromReceiptsDatastoreByEventId(this.eventId); + await deleteDocumentFromBizEventsDatastore(eventId); - let bizEventStoreResponse = await createDocumentInBizEventsDatastore(this.eventId); + let bizEventStoreResponse = await createDocumentInBizEventsDatastore(eventId, status); assert.strictEqual(bizEventStoreResponse.statusCode, 201); }); -When('biz event has been properly stored into receipt datastore after {int} ms with eventId {string}', async function (time, eventId) { - // boundary time spent by azure function to process event - await sleep(time); - this.responseToCheck = await getDocumentByIdFromReceiptsDatastore(eventId); -}); +Given('a receipt with eventId {string} stored into receipt datastore', async function (id) { + eventId = id; + // prior cancellation to avoid dirty cases + await deleteDocumentFromReceiptsDatastore(id); -Then('the receipts datastore returns the receipt', async function () { - assert.notStrictEqual(this.responseToCheck.resources.length, 0); - this.receiptId = this.responseToCheck.resources[0].id; - assert.strictEqual(this.responseToCheck.resources.length, 1); + let receiptsStoreResponse = await createDocumentInReceiptsDatastore(id); + assert.strictEqual(receiptsStoreResponse.statusCode, 201); }); -Then('the receipt has eventId {string}', function (targetId) { - assert.strictEqual(this.responseToCheck.resources[0].eventId, targetId); -}); +Given('a receipt-error with bizEventId {string} stored into receipt-error datastore', async function (id) { + eventId = id; + // prior cancellation to avoid dirty cases + await deleteDocumentFromReceiptErrorDatastore(id); -Then('the receipt has not the status {string}', function (targetStatus) { - assert.notStrictEqual(this.responseToCheck.resources[0].status, targetStatus); + let receiptsStoreResponse = await createDocumentInReceiptErrorDatastore(id); + assert.strictEqual(receiptsStoreResponse.statusCode, 201); }); -Given('a random receipt with id {string} stored with status FAILED', async function (id) { - this.eventId = id; - // prior cancellation to avoid dirty cases - document = await getDocumentByIdFromReceiptsDatastore(this.eventId); - await updateReceiptToFailed(document.resources[0].id, this.eventId); +Given("a receipt pdf with filename {string} stored into blob storage", async function (fileName){ + receiptPdfFileName = fileName; + // prior cancellation to avoid dirty cases + await deleteBlob(fileName); + + fs.writeFileSync(fileName, "", "binary"); + let blobStorageResponse = await uploadBlobFromLocalPath(fileName, fileName); + assert.notStrictEqual(blobStorageResponse.status, 500); }); -When('HTTP recovery request is called', async function () { - // boundary time spent by azure function to process event - this.response = await recoverFailedEvent(this.eventId); +//When +When("getReceipt API is called with eventId {string}", async function (id) { + responseAPI = await getReceipt(id); + receipt = responseAPI.data; }); -Then('the receipt has not the status {string} after {int} ms', async function (targetStatus, time) { - await sleep(time); - this.responseToCheck = await getDocumentByIdFromReceiptsDatastore(this.eventId); - assert.notStrictEqual(this.responseToCheck.resources[0].status, targetStatus); +When("getReceiptByOrganizationFiscalCodeAndIUV API is called with organizationFiscalCode {string} and IUV {string}", async function (orgCode, iuv) { + responseAPI = await getReceiptByOrganizationFiscalCodeAndIUV(orgCode, iuv); + receipt = responseAPI.data; }); -When('HTTP recovery request is called without eventId', async function () { - this.response = await recoverFailedEvent(null); +When("getReceiptError API is called with bizEventId {string}", async function (id) { + responseAPI = await getReceiptError(id); + receiptError = responseAPI.data; }); -Then('response has a {int} Http status', function (expectedStatus) { - assert.strictEqual(this.response.status, expectedStatus); +When("getReceiptPdf API is called with filename {string}", async function (filename) { + responseAPI = await getReceiptPdf(filename); }); -Given('a receipt with id {string} stored into receipt datastore', async function (id) { - this.eventId = id; - // prior cancellation to avoid dirty cases - await deleteDocumentFromReceiptsDatastore(this.eventId, this.eventId); +//Then +Then('the api response has a {int} Http status', function (expectedStatus) { + assert.strictEqual(responseAPI.status, expectedStatus); +}); - let receiptsStoreResponse = await createDocumentInReceiptsDatastore(this.eventId); - assert.strictEqual(receiptsStoreResponse.statusCode, 201); - this.receiptId = this.eventId; +Then('the receipt has eventId {string}', function (targetId) { + assert.strictEqual(receipt.eventId, targetId); }); -Given('a biz-event with id {string} stored into biz-event datastore', async function (id) { - this.eventId = id; - // prior cancellation to avoid dirty cases - await deleteDocumentFromBizEventsDatastore(this.eventId, this.eventId); +Then('the receipt has not the status {string}', function (targetStatus) { + assert.notStrictEqual(receipt.status, targetStatus); +}); - let receiptsStoreResponse = await createDocumentInBizEventsDatastore(this.eventId); - assert.strictEqual(receiptsStoreResponse.statusCode, 201); - this.receiptId = this.eventId; +Then('the receipt has not the status {string} after {int} ms', async function (targetStatus, time) { + await sleep(time); + responseCosmos = await getDocumentByIdFromReceiptsDatastoreByEventId(eventId); + assert.notStrictEqual(responseCosmos.resources[0].status, targetStatus); }); -When('HTTP regenerate request is called', async function () { - // boundary time spent by azure function to process event - this.response = await regeneratePdf(this.eventId); +Then('the receipts datastore returns the receipt', async function () { + assert.notStrictEqual(responseCosmos.resources.length, 0); + eventId = responseCosmos.resources[0].eventId; + assert.strictEqual(responseCosmos.resources.length, 1); }); +Then("the receipt-error has bizEventId {string}", async function (id) { + assert.strictEqual(receiptError.bizEventId, id); +}); -Then('response has a {int} Http status', function (expectedStatus) { - assert.strictEqual(this.response.status, expectedStatus); +Then("the receipt-error payload has bizEvent decrypted with eventId {string}", async function (id) { + assert.strictEqual(receiptError.messagePayload.eventId, id); }); From ded22f08c97f4242b2af565b4485ef809625ac07 Mon Sep 17 00:00:00 2001 From: svariant Date: Thu, 7 Dec 2023 12:44:44 +0100 Subject: [PATCH 06/38] [PRDP-242] Fixed APIs endpoints --- .../it/gov/pagopa/receipt/pdf/helpdesk/GetReceiptError.java | 2 +- .../gov/pagopa/receipt/pdf/helpdesk/RegenerateReceiptPdf.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/GetReceiptError.java b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/GetReceiptError.java index 444dec7..560acc5 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/GetReceiptError.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/GetReceiptError.java @@ -40,7 +40,7 @@ public GetReceiptError(ReceiptCosmosClient receiptCosmosClient) { public HttpResponseMessage run ( @HttpTrigger(name = "GetReceiptErrorFunction", methods = {HttpMethod.GET}, - route = "/errors-toreview/{bizvent-id}", + route = "errors-toreview/{bizvent-id}", authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, @BindingName("bizvent-id") String eventId, final ExecutionContext context) { diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/RegenerateReceiptPdf.java b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/RegenerateReceiptPdf.java index a2d68f4..c6eb527 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/RegenerateReceiptPdf.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/RegenerateReceiptPdf.java @@ -64,7 +64,7 @@ public RegenerateReceiptPdf(){ public HttpResponseMessage run ( @HttpTrigger(name = "RegenerateReceiptPdfTrigger", methods = {HttpMethod.POST}, - route = "/receipts/{bizevent-id}/regenerate-receipt-pdf", + route = "receipts/{bizevent-id}/regenerate-receipt-pdf", authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, @BindingName("bizevent-id") String eventId, From 871579ec34a7cf3d569037d17cabe8401449fbe3 Mon Sep 17 00:00:00 2001 From: svariant Date: Thu, 7 Dec 2023 12:46:22 +0100 Subject: [PATCH 07/38] [PRDP-242] Added AES encryption envs to readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 1d2a87c..6f90e77 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,9 @@ then replace env variables with correct values | `TOKENIZER_APIM_HEADER_KEY` | Tokenizer APIM header key | x-api-key | | `MAX_DATE_DIFF_MILLIS` | Difference in millis between the current time and the date from witch the
receipts will be fetched in massive recover operation | 360000 | | `RECOVER_FAILED_CRON` | CRON expression for timer trigger function that recover failed receipt | | +| `AES_SECRET_KEY` | AES encryption secret key | | +| `AES_SALT` | AES encryption salt | | + > to doc details about AZ fn config > see [here](https://stackoverflow.com/questions/62669672/azure-functions-what-is-the-purpose-of-having-host-json-and-local-settings-jso) From b7049f8d253594ff280a95ccddbf4d38a70953e7 Mon Sep 17 00:00:00 2001 From: svariant Date: Thu, 7 Dec 2023 13:01:22 +0100 Subject: [PATCH 08/38] [PRDP-242] Fixed getReceiptError test --- integration-test/src/features/receipt_pdf_helpdesk.feature | 2 +- .../src/step_definitions/receipt_pdf_helpdesk_step.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/integration-test/src/features/receipt_pdf_helpdesk.feature b/integration-test/src/features/receipt_pdf_helpdesk.feature index d2c672d..abd6273 100644 --- a/integration-test/src/features/receipt_pdf_helpdesk.feature +++ b/integration-test/src/features/receipt_pdf_helpdesk.feature @@ -18,7 +18,7 @@ Feature: All about payment events to recover managed by Azure functions receipt- When getReceiptError API is called with bizEventId "receipt-helpdesk-int-test-id-3" Then the api response has a 200 Http status And the receipt-error has bizEventId "receipt-helpdesk-int-test-id-3" - And the receipt-error payload has bizEvent decrypted with eventId "receipt-helpdesk-int-test-id-3" + And the receipt-error payload has bizEvent decrypted with eventId "receipt-generator-int-test-id-4" Scenario: getReceiptPdf API return receipt pdf store on blob storage Given a receipt pdf with filename "int-test-helpdesk-receipt.pdf" stored into blob storage diff --git a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js index a316fe5..6351e09 100644 --- a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js +++ b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js @@ -134,7 +134,8 @@ Then("the receipt-error has bizEventId {string}", async function (id) { }); Then("the receipt-error payload has bizEvent decrypted with eventId {string}", async function (id) { - assert.strictEqual(receiptError.messagePayload.eventId, id); + let messagePayload = JSON.parse(receiptError.messagePayload); + assert.strictEqual(messagePayload.id, id); }); From 624a6f86bdff4006aa16a6ccf8c0963ecdf32581 Mon Sep 17 00:00:00 2001 From: svariant Date: Thu, 7 Dec 2023 13:29:33 +0100 Subject: [PATCH 09/38] [PRDP-242] ReceiptToReviewed test --- .../src/features/receipt_pdf_helpdesk.feature | 16 ++++-- .../step_definitions/api_helpdesk_client.js | 16 +++++- .../receipt_pdf_helpdesk_step.js | 55 +++++++++++-------- .../receipts_datastore_client.js | 12 +++- 4 files changed, 70 insertions(+), 29 deletions(-) diff --git a/integration-test/src/features/receipt_pdf_helpdesk.feature b/integration-test/src/features/receipt_pdf_helpdesk.feature index abd6273..df66067 100644 --- a/integration-test/src/features/receipt_pdf_helpdesk.feature +++ b/integration-test/src/features/receipt_pdf_helpdesk.feature @@ -1,20 +1,20 @@ Feature: All about payment events to recover managed by Azure functions receipt-pdf-helpdesk Scenario: getReceipt API return receipt stored on datastore - Given a receipt with eventId "receipt-helpdesk-int-test-id-1" stored into receipt datastore + Given a receipt with eventId "receipt-helpdesk-int-test-id-1" and status "TO_REVIEW" stored into receipt datastore When getReceipt API is called with eventId "receipt-helpdesk-int-test-id-1" Then the api response has a 200 Http status And the receipt has eventId "receipt-helpdesk-int-test-id-1" Scenario: getReceiptByOrganizationFiscalCodeAndIUV API return receipt stored on datastore - Given a receipt with eventId "receipt-helpdesk-int-test-id-2" stored into receipt datastore - And a biz event with id "receipt-helpdesk-int-test-id-2" stored on biz-events datastore with status "DONE" + Given a receipt with eventId "receipt-helpdesk-int-test-id-2" and status "TO_REVIEW" stored into receipt datastore + And a biz event with id "receipt-helpdesk-int-test-id-2" and status "DONE" stored on biz-events datastore When getReceiptByOrganizationFiscalCodeAndIUV API is called with organizationFiscalCode "intTestOrgCode" and IUV "intTestIuv" Then the api response has a 200 Http status And the receipt has eventId "receipt-helpdesk-int-test-id-2" Scenario: getReceiptError API return receipt-error stored on datastore - Given a receipt-error with bizEventId "receipt-helpdesk-int-test-id-3" stored into receipt-error datastore + Given a receipt-error with bizEventId "receipt-helpdesk-int-test-id-3" and status "TO_REVIEW" stored into receipt-error datastore When getReceiptError API is called with bizEventId "receipt-helpdesk-int-test-id-3" Then the api response has a 200 Http status And the receipt-error has bizEventId "receipt-helpdesk-int-test-id-3" @@ -23,4 +23,10 @@ Feature: All about payment events to recover managed by Azure functions receipt- Scenario: getReceiptPdf API return receipt pdf store on blob storage Given a receipt pdf with filename "int-test-helpdesk-receipt.pdf" stored into blob storage When getReceiptPdf API is called with filename "int-test-helpdesk-receipt.pdf" - Then the api response has a 200 Http status \ No newline at end of file + Then the api response has a 200 Http status + + Scenario: receiptToReviewed API retrieve a receipt error and updates its status to REVIEWED + Given a receipt-error with bizEventId "receipt-helpdesk-int-test-id-5" and status "TO_REVIEW" stored into receipt-error datastore + When receiptToReviewed API is called with bizEventId "receipt-helpdesk-int-test-id-5" + Then the api response has a 200 Http status + And the receipt-error with bizEventId "receipt-helpdesk-int-test-id-5" has status "REVIEWED" \ No newline at end of file diff --git a/integration-test/src/step_definitions/api_helpdesk_client.js b/integration-test/src/step_definitions/api_helpdesk_client.js index f1683cf..d84c268 100644 --- a/integration-test/src/step_definitions/api_helpdesk_client.js +++ b/integration-test/src/step_definitions/api_helpdesk_client.js @@ -60,9 +60,23 @@ async function getReceiptPdf(fileName) { }); } +async function postReceiptToReviewed(eventId) { + let endpoint = process.env.RECEIPT_TO_REVIEW_ENDPOINT || "receipts-error/{event-id}/reviewed"; + endpoint = endpoint.replace("{event-id}", eventId); + + return await axios.post(helpdesk_url + endpoint, {}) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + module.exports = { getReceipt, getReceiptByOrganizationFiscalCodeAndIUV, getReceiptError, - getReceiptPdf + getReceiptPdf, + postReceiptToReviewed } \ No newline at end of file diff --git a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js index 6351e09..24c8ed1 100644 --- a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js +++ b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js @@ -1,22 +1,23 @@ const assert = require('assert'); const { After, Given, When, Then, setDefaultTimeout } = require('@cucumber/cucumber'); let fs = require('fs'); -const { sleep } = require("./common"); +const {sleep} = require("./common"); const { createDocumentInBizEventsDatastore, deleteDocumentFromBizEventsDatastore } = require("./biz_events_datastore_client"); const { - getDocumentByIdFromReceiptsDatastoreByEventId, deleteDocumentFromReceiptsDatastore, createDocumentInReceiptsDatastore, deleteMultipleDocumentsFromReceiptsDatastoreByEventId, deleteMultipleDocumentFromReceiptErrorDatastoreByEventId, createDocumentInReceiptErrorDatastore, - deleteDocumentFromReceiptErrorDatastore + deleteDocumentFromReceiptErrorDatastore, + getDocumentFromReceiptsErrorDatastoreByBizEventId } = require("./receipts_datastore_client"); const { getReceipt, getReceiptByOrganizationFiscalCodeAndIUV, getReceiptError, - getReceiptPdf + getReceiptPdf, + postReceiptToReviewed } = require("./api_helpdesk_client"); const { uploadBlobFromLocalPath, deleteBlob } = require("./blob_storage_client"); @@ -38,16 +39,24 @@ After(async function () { if (eventId != null) { await deleteDocumentFromBizEventsDatastore(eventId); await deleteMultipleDocumentsFromReceiptsDatastoreByEventId(eventId); - await deleteMultipleDocumentFromReceiptErrorDatastoreByEventId(eventId); + //await deleteMultipleDocumentFromReceiptErrorDatastoreByEventId(eventId); } if(receiptPdfFileName != null){ await deleteBlob(receiptPdfFileName); fs.unlinkSync(receiptPdfFileName); } + + eventId = null; + responseAPI = null; + responseCosmos = null; + receipt = null; + receiptError = null; + event = null; + receiptPdfFileName = null; }); //Given -Given('a biz event with id {string} stored on biz-events datastore with status {string}', async function (id, status) { +Given('a biz event with id {string} and status {string} stored on biz-events datastore', async function (id, status) { eventId = id; // prior cancellation to avoid dirty cases await deleteDocumentFromBizEventsDatastore(eventId); @@ -56,21 +65,21 @@ Given('a biz event with id {string} stored on biz-events datastore with status { assert.strictEqual(bizEventStoreResponse.statusCode, 201); }); -Given('a receipt with eventId {string} stored into receipt datastore', async function (id) { +Given('a receipt with eventId {string} and status {string} stored into receipt datastore', async function (id, status) { eventId = id; // prior cancellation to avoid dirty cases await deleteDocumentFromReceiptsDatastore(id); - let receiptsStoreResponse = await createDocumentInReceiptsDatastore(id); + let receiptsStoreResponse = await createDocumentInReceiptsDatastore(id, status); assert.strictEqual(receiptsStoreResponse.statusCode, 201); }); -Given('a receipt-error with bizEventId {string} stored into receipt-error datastore', async function (id) { +Given('a receipt-error with bizEventId {string} and status {string} stored into receipt-error datastore', async function (id, status) { eventId = id; // prior cancellation to avoid dirty cases await deleteDocumentFromReceiptErrorDatastore(id); - let receiptsStoreResponse = await createDocumentInReceiptErrorDatastore(id); + let receiptsStoreResponse = await createDocumentInReceiptErrorDatastore(id, status); assert.strictEqual(receiptsStoreResponse.statusCode, 201); }); @@ -104,6 +113,10 @@ When("getReceiptPdf API is called with filename {string}", async function (filen responseAPI = await getReceiptPdf(filename); }); +When("receiptToReviewed API is called with bizEventId {string}", async function (id) { + responseAPI = await postReceiptToReviewed(id); +}); + //Then Then('the api response has a {int} Http status', function (expectedStatus) { assert.strictEqual(responseAPI.status, expectedStatus); @@ -117,18 +130,6 @@ Then('the receipt has not the status {string}', function (targetStatus) { assert.notStrictEqual(receipt.status, targetStatus); }); -Then('the receipt has not the status {string} after {int} ms', async function (targetStatus, time) { - await sleep(time); - responseCosmos = await getDocumentByIdFromReceiptsDatastoreByEventId(eventId); - assert.notStrictEqual(responseCosmos.resources[0].status, targetStatus); -}); - -Then('the receipts datastore returns the receipt', async function () { - assert.notStrictEqual(responseCosmos.resources.length, 0); - eventId = responseCosmos.resources[0].eventId; - assert.strictEqual(responseCosmos.resources.length, 1); -}); - Then("the receipt-error has bizEventId {string}", async function (id) { assert.strictEqual(receiptError.bizEventId, id); }); @@ -138,6 +139,16 @@ Then("the receipt-error payload has bizEvent decrypted with eventId {string}", a assert.strictEqual(messagePayload.id, id); }); +Then("the receipt-error with bizEventId {string} has status {string}", async function (id, status) { + let responseCosmos = await getDocumentFromReceiptsErrorDatastoreByBizEventId(id); + assert.strictEqual(responseCosmos.resources.length > 0, true); + assert.strictEqual(responseCosmos.resources[0].status, status); +}); + +Then("wait {int} ms", async function (milliSec){ + sleep(milliSec) +}); + diff --git a/integration-test/src/step_definitions/receipts_datastore_client.js b/integration-test/src/step_definitions/receipts_datastore_client.js index 1db65df..c9ed9d8 100644 --- a/integration-test/src/step_definitions/receipts_datastore_client.js +++ b/integration-test/src/step_definitions/receipts_datastore_client.js @@ -57,6 +57,15 @@ async function createDocumentInReceiptErrorDatastore(id, status) { } } +async function getDocumentFromReceiptsErrorDatastoreByBizEventId(id) { + return await receiptErrorContainer.items + .query({ + query: "SELECT * from c WHERE c.bizEventId=@bizEventId", + parameters: [{ name: "@bizEventId", value: id }] + }) + .fetchNext(); +} + async function deleteMultipleDocumentFromReceiptErrorDatastoreByEventId(id) { let documents = await getDocumentByIdFromReceiptsDatastoreByEventId(id); @@ -100,5 +109,6 @@ module.exports = { deleteDocumentFromReceiptErrorDatastore, deleteMultipleDocumentFromReceiptErrorDatastoreByEventId, - createDocumentInReceiptErrorDatastore + createDocumentInReceiptErrorDatastore, + getDocumentFromReceiptsErrorDatastoreByBizEventId } \ No newline at end of file From 8b3a78493d24ff09acf0a6d5a92d4b5b6fb52e62 Mon Sep 17 00:00:00 2001 From: svariant Date: Thu, 7 Dec 2023 14:13:57 +0100 Subject: [PATCH 10/38] [PRDP-243] RecoverFailedReceipt tests - defined all api methods --- .../src/features/receipt_pdf_helpdesk.feature | 11 ++- .../step_definitions/api_helpdesk_client.js | 70 ++++++++++++++++++- .../receipt_pdf_helpdesk_step.js | 41 ++++++++--- .../receipts_datastore_client.js | 8 +-- 4 files changed, 113 insertions(+), 17 deletions(-) diff --git a/integration-test/src/features/receipt_pdf_helpdesk.feature b/integration-test/src/features/receipt_pdf_helpdesk.feature index df66067..af91bd6 100644 --- a/integration-test/src/features/receipt_pdf_helpdesk.feature +++ b/integration-test/src/features/receipt_pdf_helpdesk.feature @@ -29,4 +29,13 @@ Feature: All about payment events to recover managed by Azure functions receipt- Given a receipt-error with bizEventId "receipt-helpdesk-int-test-id-5" and status "TO_REVIEW" stored into receipt-error datastore When receiptToReviewed API is called with bizEventId "receipt-helpdesk-int-test-id-5" Then the api response has a 200 Http status - And the receipt-error with bizEventId "receipt-helpdesk-int-test-id-5" has status "REVIEWED" \ No newline at end of file + And the receipt-error with bizEventId "receipt-helpdesk-int-test-id-5" is recovered from datastore + And the receipt-error has not the status "TO_REVIEW" + + Scenario: recoverFailedReceipt API retrieve a receipt in status FAILED and updates its status to INSERTED + Given a receipt with eventId "receipt-helpdesk-int-test-id-6" and status "FAILED" stored into receipt datastore + And a biz event with id "receipt-helpdesk-int-test-id-6" and status "DONE" stored on biz-events datastore + When recoverFailedReceipt API is called with eventId "receipt-helpdesk-int-test-id-6" + Then the api response has a 200 Http status + And the receipt with eventId "receipt-helpdesk-int-test-id-6" is recovered from datastore + And the receipt has not the status "FAILED" \ No newline at end of file diff --git a/integration-test/src/step_definitions/api_helpdesk_client.js b/integration-test/src/step_definitions/api_helpdesk_client.js index d84c268..4b067d5 100644 --- a/integration-test/src/step_definitions/api_helpdesk_client.js +++ b/integration-test/src/step_definitions/api_helpdesk_client.js @@ -73,10 +73,78 @@ async function postReceiptToReviewed(eventId) { }); } +async function postRecoverFailedReceipt(eventId) { + let endpoint = process.env.RECOVER_FAILED_ENDPOINT || "receipts/{event-id}/recover-failed"; + endpoint = endpoint.replace("{event-id}", eventId); + + return await axios.post(helpdesk_url + endpoint, {}) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function postRecoverFailedReceiptMassive() { + let endpoint = process.env.RECOVER_FAILED_MASSIVE_ENDPOINT || "receipts/recover-failed"; + + return await axios.post(helpdesk_url + endpoint, {}) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function postRecoverNotNotifiedReceipt(eventId) { + let endpoint = process.env.RECOVER_NOT_NOTIFIED_ENDPOINT || "receipts/{event-id}/recover-not-notified"; + endpoint = endpoint.replace("{event-id}", eventId); + + return await axios.post(helpdesk_url + endpoint, {}) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function postRecoverNotNotifiedReceiptMassive() { + let endpoint = process.env.RECOVER_NOT_NOTIFIED_MASSIVE_ENDPOINT || "receipts/recover-not-notified"; + + return await axios.post(helpdesk_url + endpoint, {}) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + +async function postRegenerateReceiptPdf(eventId) { + let endpoint = process.env.REGENERATE_RECEIPT_PDF_ENDPOINT || "receipts/{bizevent-id}/regenerate-receipt-pdf"; + endpoint = endpoint.replace("{bizevent-id}", eventId); + + return await axios.post(helpdesk_url + endpoint, {}) + .then(res => { + return res; + }) + .catch(error => { + return error.response; + }); +} + module.exports = { getReceipt, getReceiptByOrganizationFiscalCodeAndIUV, getReceiptError, getReceiptPdf, - postReceiptToReviewed + postReceiptToReviewed, + postRecoverFailedReceipt, + postRecoverFailedReceiptMassive, + postRecoverNotNotifiedReceipt, + postRecoverNotNotifiedReceiptMassive, + postRegenerateReceiptPdf } \ No newline at end of file diff --git a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js index 24c8ed1..8f1c404 100644 --- a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js +++ b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js @@ -10,14 +10,20 @@ const { deleteMultipleDocumentFromReceiptErrorDatastoreByEventId, createDocumentInReceiptErrorDatastore, deleteDocumentFromReceiptErrorDatastore, - getDocumentFromReceiptsErrorDatastoreByBizEventId + getDocumentFromReceiptsErrorDatastoreByBizEventId, + getDocumentFromReceiptsDatastoreByEventId } = require("./receipts_datastore_client"); const { - getReceipt, - getReceiptByOrganizationFiscalCodeAndIUV, - getReceiptError, - getReceiptPdf, - postReceiptToReviewed + getReceipt, + getReceiptByOrganizationFiscalCodeAndIUV, + getReceiptError, + getReceiptPdf, + postReceiptToReviewed, + postRecoverFailedReceipt, + postRecoverFailedReceiptMassive, + postRecoverNotNotifiedReceipt, + postRecoverNotNotifiedReceiptMassive, + postRegenerateReceiptPdf } = require("./api_helpdesk_client"); const { uploadBlobFromLocalPath, deleteBlob } = require("./blob_storage_client"); @@ -27,7 +33,6 @@ setDefaultTimeout(360 * 1000); // initialize variables let eventId = null; let responseAPI = null; -let responseCosmos = null; let receipt = null; let receiptError = null; let event = null; @@ -39,7 +44,7 @@ After(async function () { if (eventId != null) { await deleteDocumentFromBizEventsDatastore(eventId); await deleteMultipleDocumentsFromReceiptsDatastoreByEventId(eventId); - //await deleteMultipleDocumentFromReceiptErrorDatastoreByEventId(eventId); + await deleteMultipleDocumentFromReceiptErrorDatastoreByEventId(eventId); } if(receiptPdfFileName != null){ await deleteBlob(receiptPdfFileName); @@ -48,7 +53,6 @@ After(async function () { eventId = null; responseAPI = null; - responseCosmos = null; receipt = null; receiptError = null; event = null; @@ -117,6 +121,10 @@ When("receiptToReviewed API is called with bizEventId {string}", async function responseAPI = await postReceiptToReviewed(id); }); +When("recoverFailedReceipt API is called with eventId {string}", async function (id){ + responseAPI = await postRecoverFailedReceipt(id); +}); + //Then Then('the api response has a {int} Http status', function (expectedStatus) { assert.strictEqual(responseAPI.status, expectedStatus); @@ -139,12 +147,23 @@ Then("the receipt-error payload has bizEvent decrypted with eventId {string}", a assert.strictEqual(messagePayload.id, id); }); -Then("the receipt-error with bizEventId {string} has status {string}", async function (id, status) { +Then("the receipt-error has not the status {string}", async function (status) { + assert.notStrictEqual(receiptError.status, status); +}); + +Then("the receipt-error with bizEventId {string} is recovered from datastore", async function (id) { let responseCosmos = await getDocumentFromReceiptsErrorDatastoreByBizEventId(id); assert.strictEqual(responseCosmos.resources.length > 0, true); - assert.strictEqual(responseCosmos.resources[0].status, status); + receiptError = responseCosmos.resources[0]; }); +Then("the receipt with eventId {string} is recovered from datastore", async function (id) { + let responseCosmos = await getDocumentFromReceiptsDatastoreByEventId(id); + assert.strictEqual(responseCosmos.resources.length > 0, true); + receipt = responseCosmos.resources[0]; +}); + + Then("wait {int} ms", async function (milliSec){ sleep(milliSec) }); diff --git a/integration-test/src/step_definitions/receipts_datastore_client.js b/integration-test/src/step_definitions/receipts_datastore_client.js index c9ed9d8..db94bfc 100644 --- a/integration-test/src/step_definitions/receipts_datastore_client.js +++ b/integration-test/src/step_definitions/receipts_datastore_client.js @@ -20,7 +20,7 @@ async function createDocumentInReceiptsDatastore(id, status) { } } -async function getDocumentByIdFromReceiptsDatastoreByEventId(id) { +async function getDocumentFromReceiptsDatastoreByEventId(id) { return await receiptContainer.items .query({ query: "SELECT * from c WHERE c.eventId=@eventId", @@ -30,7 +30,7 @@ async function getDocumentByIdFromReceiptsDatastoreByEventId(id) { } async function deleteMultipleDocumentsFromReceiptsDatastoreByEventId(eventId) { - let documents = await getDocumentByIdFromReceiptsDatastoreByEventId(eventId); + let documents = await getDocumentFromReceiptsDatastoreByEventId(eventId); documents?.resources?.forEach(el => { deleteDocumentFromReceiptsDatastore(el.id); @@ -67,7 +67,7 @@ async function getDocumentFromReceiptsErrorDatastoreByBizEventId(id) { } async function deleteMultipleDocumentFromReceiptErrorDatastoreByEventId(id) { - let documents = await getDocumentByIdFromReceiptsDatastoreByEventId(id); + let documents = await getDocumentFromReceiptsErrorDatastoreByBizEventId(id); documents?.resources?.forEach(el => { deleteDocumentFromReceiptErrorDatastore(el.id); @@ -102,7 +102,7 @@ async function updateReceiptToFailed(id) { module.exports = { createDocumentInReceiptsDatastore, - getDocumentByIdFromReceiptsDatastoreByEventId, + getDocumentFromReceiptsDatastoreByEventId, deleteMultipleDocumentsFromReceiptsDatastoreByEventId, deleteDocumentFromReceiptsDatastore, updateReceiptToFailed, From 84b1036121f139fc34c90829cc84ad47c8f891f0 Mon Sep 17 00:00:00 2001 From: svariant Date: Thu, 7 Dec 2023 14:59:57 +0100 Subject: [PATCH 11/38] [PRDP-242] RecoverFailedMassive test --- .../src/features/receipt_pdf_helpdesk.feature | 11 ++- .../step_definitions/api_helpdesk_client.js | 5 +- .../src/step_definitions/common.js | 9 +- .../receipt_pdf_helpdesk_step.js | 91 +++++++++++++++---- 4 files changed, 89 insertions(+), 27 deletions(-) diff --git a/integration-test/src/features/receipt_pdf_helpdesk.feature b/integration-test/src/features/receipt_pdf_helpdesk.feature index af91bd6..dbd71ba 100644 --- a/integration-test/src/features/receipt_pdf_helpdesk.feature +++ b/integration-test/src/features/receipt_pdf_helpdesk.feature @@ -30,7 +30,7 @@ Feature: All about payment events to recover managed by Azure functions receipt- When receiptToReviewed API is called with bizEventId "receipt-helpdesk-int-test-id-5" Then the api response has a 200 Http status And the receipt-error with bizEventId "receipt-helpdesk-int-test-id-5" is recovered from datastore - And the receipt-error has not the status "TO_REVIEW" + And the receipt-error has not status "TO_REVIEW" Scenario: recoverFailedReceipt API retrieve a receipt in status FAILED and updates its status to INSERTED Given a receipt with eventId "receipt-helpdesk-int-test-id-6" and status "FAILED" stored into receipt datastore @@ -38,4 +38,11 @@ Feature: All about payment events to recover managed by Azure functions receipt- When recoverFailedReceipt API is called with eventId "receipt-helpdesk-int-test-id-6" Then the api response has a 200 Http status And the receipt with eventId "receipt-helpdesk-int-test-id-6" is recovered from datastore - And the receipt has not the status "FAILED" \ No newline at end of file + And the receipt has not status "FAILED" + + Scenario: recoverFailedReceiptMassive API retrieve all the receipts in status FAILED and updates their status to INSERTED + Given a list of 10 receipts in status "FAILED" stored into receipt datastore starting from eventId "receipt-helpdesk-int-test-id-7" + And a list of 10 biz events in status "DONE" stored into biz-events datastore starting from eventId "receipt-helpdesk-int-test-id-7" + When recoverFailedReceiptMassive API is called with status "FAILED" as query param + Then the api response has a 200 Http status + And the list of receipt is recovered from datastore and no receipt in the list has status "FAILED" \ No newline at end of file diff --git a/integration-test/src/step_definitions/api_helpdesk_client.js b/integration-test/src/step_definitions/api_helpdesk_client.js index 4b067d5..141a78e 100644 --- a/integration-test/src/step_definitions/api_helpdesk_client.js +++ b/integration-test/src/step_definitions/api_helpdesk_client.js @@ -86,8 +86,9 @@ async function postRecoverFailedReceipt(eventId) { }); } -async function postRecoverFailedReceiptMassive() { - let endpoint = process.env.RECOVER_FAILED_MASSIVE_ENDPOINT || "receipts/recover-failed"; +async function postRecoverFailedReceiptMassive(status) { + let endpoint = process.env.RECOVER_FAILED_MASSIVE_ENDPOINT || "receipts/recover-failed?status={STATUS}"; + endpoint = endpoint.replace("{STATUS}", status); return await axios.post(helpdesk_url + endpoint, {}) .then(res => { diff --git a/integration-test/src/step_definitions/common.js b/integration-test/src/step_definitions/common.js index b208035..611c6cc 100644 --- a/integration-test/src/step_definitions/common.js +++ b/integration-test/src/step_definitions/common.js @@ -1,4 +1,5 @@ const TOKENIZED_FISCAL_CODE = "cd07268c-73e8-4df4-8305-a35085e32eff"; +const FISCAL_CODE = "AAAAAA00A00A000A"; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); @@ -30,7 +31,7 @@ function createEvent(id, status) { "officeName": "office" }, "psp": { - "idPsp": "60000000001", + "idPsp": "BNLIITRR", "idBrokerPsp": "60000000001", "idChannel": "60000000001_08", "psp": "PSP Paolo", @@ -40,7 +41,7 @@ function createEvent(id, status) { "debtor": { "fullName": "paGetPaymentName", "entityUniqueIdentifierType": "G", - "entityUniqueIdentifierValue": "JHNDOE00A01F205N", + "entityUniqueIdentifierValue": FISCAL_CODE, "streetName": "paGetPaymentStreet", "civicNumber": "paGetPayment99", "postalCode": "20155", @@ -52,7 +53,7 @@ function createEvent(id, status) { "payer": { "fullName": "name", "entityUniqueIdentifierType": "G", - "entityUniqueIdentifierValue": "JHNDOE00A01F205S", + "entityUniqueIdentifierValue": FISCAL_CODE, "streetName": "street", "civicNumber": "civic", "postalCode": "postal", @@ -95,7 +96,7 @@ function createEvent(id, status) { "user": { "fullName": "John Doe", "type": "F", - "fiscalCode": "JHNDOE00A01F205N", + "fiscalCode": FISCAL_CODE, "notificationEmail": "john.doe@mail.it", "userId": "1234", "userStatus": "11", diff --git a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js index 8f1c404..ae3317b 100644 --- a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js +++ b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js @@ -1,7 +1,7 @@ const assert = require('assert'); const { After, Given, When, Then, setDefaultTimeout } = require('@cucumber/cucumber'); let fs = require('fs'); -const {sleep} = require("./common"); +const { sleep } = require("./common"); const { createDocumentInBizEventsDatastore, deleteDocumentFromBizEventsDatastore } = require("./biz_events_datastore_client"); const { deleteDocumentFromReceiptsDatastore, @@ -11,19 +11,19 @@ const { createDocumentInReceiptErrorDatastore, deleteDocumentFromReceiptErrorDatastore, getDocumentFromReceiptsErrorDatastoreByBizEventId, - getDocumentFromReceiptsDatastoreByEventId + getDocumentFromReceiptsDatastoreByEventId, } = require("./receipts_datastore_client"); const { - getReceipt, - getReceiptByOrganizationFiscalCodeAndIUV, - getReceiptError, - getReceiptPdf, - postReceiptToReviewed, - postRecoverFailedReceipt, - postRecoverFailedReceiptMassive, - postRecoverNotNotifiedReceipt, - postRecoverNotNotifiedReceiptMassive, - postRegenerateReceiptPdf + getReceipt, + getReceiptByOrganizationFiscalCodeAndIUV, + getReceiptError, + getReceiptPdf, + postReceiptToReviewed, + postRecoverFailedReceipt, + postRecoverFailedReceiptMassive, + postRecoverNotNotifiedReceipt, + postRecoverNotNotifiedReceiptMassive, + postRegenerateReceiptPdf } = require("./api_helpdesk_client"); const { uploadBlobFromLocalPath, deleteBlob } = require("./blob_storage_client"); @@ -37,6 +37,8 @@ let receipt = null; let receiptError = null; let event = null; let receiptPdfFileName = null; +let listOfReceipts = []; +let listOfBizEvents = []; // After each Scenario After(async function () { @@ -46,10 +48,20 @@ After(async function () { await deleteMultipleDocumentsFromReceiptsDatastoreByEventId(eventId); await deleteMultipleDocumentFromReceiptErrorDatastoreByEventId(eventId); } - if(receiptPdfFileName != null){ + if (receiptPdfFileName != null) { await deleteBlob(receiptPdfFileName); fs.unlinkSync(receiptPdfFileName); } + if(listOfReceipts.length > 0){ + for(let receipt of listOfReceipts){ + await deleteDocumentFromReceiptsDatastore(receipt.id); + } + } + if(listOfBizEvents.length > 0){ + for(let bizEvent of listOfBizEvents){ + await deleteDocumentFromBizEventsDatastore(bizEvent.id); + } + } eventId = null; responseAPI = null; @@ -57,6 +69,8 @@ After(async function () { receiptError = null; event = null; receiptPdfFileName = null; + listOfReceipts = []; + listOfBizEvents = []; }); //Given @@ -87,9 +101,9 @@ Given('a receipt-error with bizEventId {string} and status {string} stored into assert.strictEqual(receiptsStoreResponse.statusCode, 201); }); -Given("a receipt pdf with filename {string} stored into blob storage", async function (fileName){ +Given("a receipt pdf with filename {string} stored into blob storage", async function (fileName) { receiptPdfFileName = fileName; - // prior cancellation to avoid dirty cases + // prior cancellation to avoid dirty cases await deleteBlob(fileName); fs.writeFileSync(fileName, "", "binary"); @@ -97,6 +111,34 @@ Given("a receipt pdf with filename {string} stored into blob storage", async fun assert.notStrictEqual(blobStorageResponse.status, 500); }); +Given("a list of {int} receipts in status {string} stored into receipt datastore starting from eventId {string}", async function (numberOfReceipts, status, startingId) { + listOfReceipts = []; + for (let i = 0; i < numberOfReceipts; i++) { + let nextEventId = startingId + i; + // prior cancellation to avoid dirty cases + await deleteMultipleDocumentsFromReceiptsDatastoreByEventId(nextEventId); + + let receiptsStoreResponse = await createDocumentInReceiptsDatastore(nextEventId, status); + assert.strictEqual(receiptsStoreResponse.statusCode, 201); + + listOfReceipts.push(receiptsStoreResponse.resource); + } +}); + +Given("a list of {int} biz events in status {string} stored into biz-events datastore starting from eventId {string}", async function (numberOfEvents, status, startingId) { + listOfBizEvents = []; + for (let i = 0; i < numberOfEvents; i++) { + let nextEventId = startingId + i; + // prior cancellation to avoid dirty cases + await deleteDocumentFromBizEventsDatastore(nextEventId); + + let bizEventStoreResponse = await createDocumentInBizEventsDatastore(nextEventId, status); + assert.strictEqual(bizEventStoreResponse.statusCode, 201); + + listOfBizEvents.push(bizEventStoreResponse.resource); + } +}); + //When When("getReceipt API is called with eventId {string}", async function (id) { responseAPI = await getReceipt(id); @@ -121,10 +163,14 @@ When("receiptToReviewed API is called with bizEventId {string}", async function responseAPI = await postReceiptToReviewed(id); }); -When("recoverFailedReceipt API is called with eventId {string}", async function (id){ +When("recoverFailedReceipt API is called with eventId {string}", async function (id) { responseAPI = await postRecoverFailedReceipt(id); }); +When("recoverFailedReceiptMassive API is called with status {string} as query param", async function (status) { + responseAPI = await postRecoverFailedReceiptMassive(status); +}); + //Then Then('the api response has a {int} Http status', function (expectedStatus) { assert.strictEqual(responseAPI.status, expectedStatus); @@ -134,7 +180,7 @@ Then('the receipt has eventId {string}', function (targetId) { assert.strictEqual(receipt.eventId, targetId); }); -Then('the receipt has not the status {string}', function (targetStatus) { +Then('the receipt has not status {string}', function (targetStatus) { assert.notStrictEqual(receipt.status, targetStatus); }); @@ -147,7 +193,7 @@ Then("the receipt-error payload has bizEvent decrypted with eventId {string}", a assert.strictEqual(messagePayload.id, id); }); -Then("the receipt-error has not the status {string}", async function (status) { +Then("the receipt-error has not status {string}", async function (status) { assert.notStrictEqual(receiptError.status, status); }); @@ -163,8 +209,15 @@ Then("the receipt with eventId {string} is recovered from datastore", async func receipt = responseCosmos.resources[0]; }); +Then("the list of receipt is recovered from datastore and no receipt in the list has status {string}", async function (status) { + for(let recoveredReceipt of listOfReceipts){ + let responseCosmos = await getDocumentFromReceiptsDatastoreByEventId(recoveredReceipt.eventId); + assert.strictEqual(responseCosmos.resources.length > 0, true); + assert.notStrictEqual(responseCosmos.resources[0].status, status); + } +}); -Then("wait {int} ms", async function (milliSec){ +Then("wait {int} ms", async function (milliSec) { sleep(milliSec) }); From 2390eb67cb69843f5281dfaa4e15174db45ba525 Mon Sep 17 00:00:00 2001 From: svariant Date: Thu, 7 Dec 2023 15:04:36 +0100 Subject: [PATCH 12/38] [PRDP-242] RecoverNotNotifedReceipt test --- .../src/features/receipt_pdf_helpdesk.feature | 14 +++++++++++--- .../step_definitions/receipt_pdf_helpdesk_step.js | 4 ++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/integration-test/src/features/receipt_pdf_helpdesk.feature b/integration-test/src/features/receipt_pdf_helpdesk.feature index dbd71ba..a0566e0 100644 --- a/integration-test/src/features/receipt_pdf_helpdesk.feature +++ b/integration-test/src/features/receipt_pdf_helpdesk.feature @@ -32,7 +32,7 @@ Feature: All about payment events to recover managed by Azure functions receipt- And the receipt-error with bizEventId "receipt-helpdesk-int-test-id-5" is recovered from datastore And the receipt-error has not status "TO_REVIEW" - Scenario: recoverFailedReceipt API retrieve a receipt in status FAILED and updates its status to INSERTED + Scenario: recoverFailedReceipt API retrieve a receipt in status FAILED and updates its status Given a receipt with eventId "receipt-helpdesk-int-test-id-6" and status "FAILED" stored into receipt datastore And a biz event with id "receipt-helpdesk-int-test-id-6" and status "DONE" stored on biz-events datastore When recoverFailedReceipt API is called with eventId "receipt-helpdesk-int-test-id-6" @@ -40,9 +40,17 @@ Feature: All about payment events to recover managed by Azure functions receipt- And the receipt with eventId "receipt-helpdesk-int-test-id-6" is recovered from datastore And the receipt has not status "FAILED" - Scenario: recoverFailedReceiptMassive API retrieve all the receipts in status FAILED and updates their status to INSERTED + Scenario: recoverFailedReceiptMassive API retrieve all the receipts in status FAILED and updates their status Given a list of 10 receipts in status "FAILED" stored into receipt datastore starting from eventId "receipt-helpdesk-int-test-id-7" And a list of 10 biz events in status "DONE" stored into biz-events datastore starting from eventId "receipt-helpdesk-int-test-id-7" When recoverFailedReceiptMassive API is called with status "FAILED" as query param Then the api response has a 200 Http status - And the list of receipt is recovered from datastore and no receipt in the list has status "FAILED" \ No newline at end of file + And the list of receipt is recovered from datastore and no receipt in the list has status "FAILED" + + Scenario: recoverNotNotifiedReceipt API retrieve a receipt in status IO_ERROR_TO_NOTIFY and updates its status + Given a receipt with eventId "receipt-helpdesk-int-test-id-8" and status "IO_ERROR_TO_NOTIFY" stored into receipt datastore + And a biz event with id "receipt-helpdesk-int-test-id-8" and status "DONE" stored on biz-events datastore + When recoverNotNotifiedReceipt API is called with eventId "receipt-helpdesk-int-test-id-8" + Then the api response has a 200 Http status + And the receipt with eventId "receipt-helpdesk-int-test-id-8" is recovered from datastore + And the receipt has not status "IO_ERROR_TO_NOTIFY" \ No newline at end of file diff --git a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js index ae3317b..63c2a29 100644 --- a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js +++ b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js @@ -171,6 +171,10 @@ When("recoverFailedReceiptMassive API is called with status {string} as query pa responseAPI = await postRecoverFailedReceiptMassive(status); }); +When("recoverNotNotifiedReceipt API is called with eventId {string}", async function (id) { + responseAPI = await postRecoverNotNotifiedReceipt(id); +}); + //Then Then('the api response has a {int} Http status', function (expectedStatus) { assert.strictEqual(responseAPI.status, expectedStatus); From 5708d27938ddf7f34386f966e321c59bb9968123 Mon Sep 17 00:00:00 2001 From: svariant Date: Thu, 7 Dec 2023 17:00:55 +0100 Subject: [PATCH 13/38] [PRDP-242] RecoverNotNotifedMassive test --- .../src/features/receipt_pdf_helpdesk.feature | 9 ++++- .../step_definitions/api_helpdesk_client.js | 5 +-- .../biz_events_datastore_client.js | 21 +++++++++-- .../src/step_definitions/common.js | 10 +++++- .../receipt_pdf_helpdesk_step.js | 17 ++++----- .../receipts_datastore_client.js | 35 ++++++++++++++++++- 6 files changed, 81 insertions(+), 16 deletions(-) diff --git a/integration-test/src/features/receipt_pdf_helpdesk.feature b/integration-test/src/features/receipt_pdf_helpdesk.feature index a0566e0..cd20f9f 100644 --- a/integration-test/src/features/receipt_pdf_helpdesk.feature +++ b/integration-test/src/features/receipt_pdf_helpdesk.feature @@ -53,4 +53,11 @@ Feature: All about payment events to recover managed by Azure functions receipt- When recoverNotNotifiedReceipt API is called with eventId "receipt-helpdesk-int-test-id-8" Then the api response has a 200 Http status And the receipt with eventId "receipt-helpdesk-int-test-id-8" is recovered from datastore - And the receipt has not status "IO_ERROR_TO_NOTIFY" \ No newline at end of file + And the receipt has not status "IO_ERROR_TO_NOTIFY" + +Scenario: recoverNotNotifiedReceiptMassive API retrieve all the receipts in status IO_ERROR_TO_NOTIFY and updates their status + Given a list of 10 receipts in status "IO_ERROR_TO_NOTIFY" stored into receipt datastore starting from eventId "receipt-helpdesk-int-test-id-9" + And a list of 10 biz events in status "DONE" stored into biz-events datastore starting from eventId "receipt-helpdesk-int-test-id-9" + When recoverNotNotifiedReceiptMassive API is called with status "IO_ERROR_TO_NOTIFY" as query param + Then the api response has a 200 Http status + And the list of receipt is recovered from datastore and no receipt in the list has status "IO_ERROR_TO_NOTIFY" diff --git a/integration-test/src/step_definitions/api_helpdesk_client.js b/integration-test/src/step_definitions/api_helpdesk_client.js index 141a78e..cd15b40 100644 --- a/integration-test/src/step_definitions/api_helpdesk_client.js +++ b/integration-test/src/step_definitions/api_helpdesk_client.js @@ -112,8 +112,9 @@ async function postRecoverNotNotifiedReceipt(eventId) { }); } -async function postRecoverNotNotifiedReceiptMassive() { - let endpoint = process.env.RECOVER_NOT_NOTIFIED_MASSIVE_ENDPOINT || "receipts/recover-not-notified"; +async function postRecoverNotNotifiedReceiptMassive(status) { + let endpoint = process.env.RECOVER_NOT_NOTIFIED_MASSIVE_ENDPOINT || "receipts/recover-not-notified?status={STATUS}"; + endpoint = endpoint.replace("{STATUS}", status); return await axios.post(helpdesk_url + endpoint, {}) .then(res => { diff --git a/integration-test/src/step_definitions/biz_events_datastore_client.js b/integration-test/src/step_definitions/biz_events_datastore_client.js index f6bc909..28fa3c7 100644 --- a/integration-test/src/step_definitions/biz_events_datastore_client.js +++ b/integration-test/src/step_definitions/biz_events_datastore_client.js @@ -2,8 +2,8 @@ const { CosmosClient } = require("@azure/cosmos"); const { createEvent } = require("./common"); const cosmos_db_conn_string = process.env.BIZEVENTS_COSMOS_CONN_STRING; -const databaseId = process.env.BIZ_EVENT_COSMOS_DB_NAME; // es. db -const containerId = process.env.BIZ_EVENT_COSMOS_DB_CONTAINER_NAME; // es. biz-events +const databaseId = process.env.BIZ_EVENT_COSMOS_DB_NAME; // es. db +const containerId = process.env.BIZ_EVENT_COSMOS_DB_CONTAINER_NAME; // es. biz-events const client = new CosmosClient(cosmos_db_conn_string); const container = client.database(databaseId).container(containerId); @@ -36,6 +36,21 @@ async function deleteDocumentFromBizEventsDatastore(id) { } } +async function deleteAllTestBizEvents() { + let responseCosmos = await container.items.query({ + query: 'SELECT * from c WHERE c.id LIKE @id', + parameters: [{ name: "@id", value: "%receipt-helpdesk-int-test%" }] + }).fetchAll(); + + let eventList = responseCosmos.resources; + if (eventList.length > 0) { + eventList.forEach((event) => { + console.log("\n Deleting bizEvent with id " + id); + deleteDocumentFromBizEventsDatastore(event.id) + }) + } +} + module.exports = { - getDocumentByIdFromBizEventsDatastore, createDocumentInBizEventsDatastore, deleteDocumentFromBizEventsDatastore + getDocumentByIdFromBizEventsDatastore, createDocumentInBizEventsDatastore, deleteDocumentFromBizEventsDatastore, deleteAllTestBizEvents } \ No newline at end of file diff --git a/integration-test/src/step_definitions/common.js b/integration-test/src/step_definitions/common.js index 611c6cc..0221f72 100644 --- a/integration-test/src/step_definitions/common.js +++ b/integration-test/src/step_definitions/common.js @@ -5,6 +5,13 @@ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } +function getRandomInt(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min) + min); // The maximum is exclusive and the minimum is inclusive + } + + function createEvent(id, status) { let json_event = { "id": id, @@ -169,5 +176,6 @@ module.exports = { createEvent, sleep, createReceipt, - createReceiptError + createReceiptError, + getRandomInt } \ No newline at end of file diff --git a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js index 63c2a29..1a664a1 100644 --- a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js +++ b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js @@ -2,16 +2,17 @@ const assert = require('assert'); const { After, Given, When, Then, setDefaultTimeout } = require('@cucumber/cucumber'); let fs = require('fs'); const { sleep } = require("./common"); -const { createDocumentInBizEventsDatastore, deleteDocumentFromBizEventsDatastore } = require("./biz_events_datastore_client"); +const { createDocumentInBizEventsDatastore, deleteDocumentFromBizEventsDatastore, deleteAllTestBizEvents } = require("./biz_events_datastore_client"); const { deleteDocumentFromReceiptsDatastore, createDocumentInReceiptsDatastore, deleteMultipleDocumentsFromReceiptsDatastoreByEventId, - deleteMultipleDocumentFromReceiptErrorDatastoreByEventId, createDocumentInReceiptErrorDatastore, deleteDocumentFromReceiptErrorDatastore, getDocumentFromReceiptsErrorDatastoreByBizEventId, getDocumentFromReceiptsDatastoreByEventId, + deleteAllTestReceipts, + deleteAllTestReceiptsError } = require("./receipts_datastore_client"); const { getReceipt, @@ -38,10 +39,10 @@ let receiptError = null; let event = null; let receiptPdfFileName = null; let listOfReceipts = []; -let listOfBizEvents = []; // After each Scenario After(async function () { + sleep(10000); // remove event if (eventId != null) { await deleteDocumentFromBizEventsDatastore(eventId); @@ -70,7 +71,6 @@ After(async function () { event = null; receiptPdfFileName = null; listOfReceipts = []; - listOfBizEvents = []; }); //Given @@ -126,7 +126,6 @@ Given("a list of {int} receipts in status {string} stored into receipt datastore }); Given("a list of {int} biz events in status {string} stored into biz-events datastore starting from eventId {string}", async function (numberOfEvents, status, startingId) { - listOfBizEvents = []; for (let i = 0; i < numberOfEvents; i++) { let nextEventId = startingId + i; // prior cancellation to avoid dirty cases @@ -134,8 +133,6 @@ Given("a list of {int} biz events in status {string} stored into biz-events data let bizEventStoreResponse = await createDocumentInBizEventsDatastore(nextEventId, status); assert.strictEqual(bizEventStoreResponse.statusCode, 201); - - listOfBizEvents.push(bizEventStoreResponse.resource); } }); @@ -175,6 +172,10 @@ When("recoverNotNotifiedReceipt API is called with eventId {string}", async func responseAPI = await postRecoverNotNotifiedReceipt(id); }); +When("recoverNotNotifiedReceiptMassive API is called with status {string} as query param", async function (status) { + responseAPI = await postRecoverNotNotifiedReceiptMassive(status); +}); + //Then Then('the api response has a {int} Http status', function (expectedStatus) { assert.strictEqual(responseAPI.status, expectedStatus); @@ -214,7 +215,7 @@ Then("the receipt with eventId {string} is recovered from datastore", async func }); Then("the list of receipt is recovered from datastore and no receipt in the list has status {string}", async function (status) { - for(let recoveredReceipt of listOfReceipts){ + for (let recoveredReceipt of listOfReceipts) { let responseCosmos = await getDocumentFromReceiptsDatastoreByEventId(recoveredReceipt.eventId); assert.strictEqual(responseCosmos.resources.length > 0, true); assert.notStrictEqual(responseCosmos.resources[0].status, status); diff --git a/integration-test/src/step_definitions/receipts_datastore_client.js b/integration-test/src/step_definitions/receipts_datastore_client.js index db94bfc..74a15f8 100644 --- a/integration-test/src/step_definitions/receipts_datastore_client.js +++ b/integration-test/src/step_definitions/receipts_datastore_client.js @@ -100,15 +100,48 @@ async function updateReceiptToFailed(id) { } } +async function deleteAllTestReceipts() { + let response = await receiptContainer.items.query({ + query: 'SELECT * from c WHERE c.id LIKE @id', + parameters: [{ name: "@id", value: "%receipt-helpdesk-int-test%" }] + }).fetchNext(); + + + let receiptList = response.resources; + if (receiptList.length > 0) { + receiptList.forEach((receipt) => { + console.log("\n Deleting receipt with id " + id); + deleteDocumentFromReceiptsDatastore(receipt.id); + }); + } +} + +async function deleteAllTestReceiptsError() { + let response = await receiptErrorContainer.items.query({ + query: 'SELECT * from c WHERE c.bizEventId LIKE @bizEventId', + parameters: [{ name: "@bizEventId", value: "%receipt-helpdesk-int-test%" }] + }).fetchNext(); + + let receiptErrorList = response.resources; + if (receiptErrorList.length > 0) { + receiptErrorList.forEach((receiptError) => { + console.log("\n Deleting receiptError with id " + id); + deleteDocumentFromReceiptErrorDatastore(receiptError.id); + }) + } +} + module.exports = { createDocumentInReceiptsDatastore, getDocumentFromReceiptsDatastoreByEventId, deleteMultipleDocumentsFromReceiptsDatastoreByEventId, deleteDocumentFromReceiptsDatastore, updateReceiptToFailed, + deleteAllTestReceipts, deleteDocumentFromReceiptErrorDatastore, deleteMultipleDocumentFromReceiptErrorDatastoreByEventId, createDocumentInReceiptErrorDatastore, - getDocumentFromReceiptsErrorDatastoreByBizEventId + getDocumentFromReceiptsErrorDatastoreByBizEventId, + deleteAllTestReceiptsError } \ No newline at end of file From 05f1c8cf2cc94d3ff183229ec3a73c4bfaa578cc Mon Sep 17 00:00:00 2001 From: svariant Date: Thu, 7 Dec 2023 17:22:44 +0100 Subject: [PATCH 14/38] [PRDP-242] Improved steps --- .../src/features/receipt_pdf_helpdesk.feature | 10 ++++----- .../biz_events_datastore_client.js | 4 ++-- .../src/step_definitions/common.js | 6 ++--- .../receipt_pdf_helpdesk_step.js | 22 ++++++++++++------- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/integration-test/src/features/receipt_pdf_helpdesk.feature b/integration-test/src/features/receipt_pdf_helpdesk.feature index cd20f9f..ef29405 100644 --- a/integration-test/src/features/receipt_pdf_helpdesk.feature +++ b/integration-test/src/features/receipt_pdf_helpdesk.feature @@ -8,7 +8,7 @@ Feature: All about payment events to recover managed by Azure functions receipt- Scenario: getReceiptByOrganizationFiscalCodeAndIUV API return receipt stored on datastore Given a receipt with eventId "receipt-helpdesk-int-test-id-2" and status "TO_REVIEW" stored into receipt datastore - And a biz event with id "receipt-helpdesk-int-test-id-2" and status "DONE" stored on biz-events datastore + And a biz event with id "receipt-helpdesk-int-test-id-2" and status "DONE" and organizationFiscalCode "intTestOrgCode" and IUV "intTestIuv" stored on biz-events datastore When getReceiptByOrganizationFiscalCodeAndIUV API is called with organizationFiscalCode "intTestOrgCode" and IUV "intTestIuv" Then the api response has a 200 Http status And the receipt has eventId "receipt-helpdesk-int-test-id-2" @@ -41,8 +41,8 @@ Feature: All about payment events to recover managed by Azure functions receipt- And the receipt has not status "FAILED" Scenario: recoverFailedReceiptMassive API retrieve all the receipts in status FAILED and updates their status - Given a list of 10 receipts in status "FAILED" stored into receipt datastore starting from eventId "receipt-helpdesk-int-test-id-7" - And a list of 10 biz events in status "DONE" stored into biz-events datastore starting from eventId "receipt-helpdesk-int-test-id-7" + Given a list of 5 receipts in status "FAILED" stored into receipt datastore starting from eventId "receipt-helpdesk-int-test-id-7" + And a list of 5 biz events in status "DONE" stored into biz-events datastore starting from eventId "receipt-helpdesk-int-test-id-7" When recoverFailedReceiptMassive API is called with status "FAILED" as query param Then the api response has a 200 Http status And the list of receipt is recovered from datastore and no receipt in the list has status "FAILED" @@ -56,8 +56,8 @@ Feature: All about payment events to recover managed by Azure functions receipt- And the receipt has not status "IO_ERROR_TO_NOTIFY" Scenario: recoverNotNotifiedReceiptMassive API retrieve all the receipts in status IO_ERROR_TO_NOTIFY and updates their status - Given a list of 10 receipts in status "IO_ERROR_TO_NOTIFY" stored into receipt datastore starting from eventId "receipt-helpdesk-int-test-id-9" - And a list of 10 biz events in status "DONE" stored into biz-events datastore starting from eventId "receipt-helpdesk-int-test-id-9" + Given a list of 5 receipts in status "IO_ERROR_TO_NOTIFY" stored into receipt datastore starting from eventId "receipt-helpdesk-int-test-id-9" + And a list of 5 biz events in status "DONE" stored into biz-events datastore starting from eventId "receipt-helpdesk-int-test-id-9" When recoverNotNotifiedReceiptMassive API is called with status "IO_ERROR_TO_NOTIFY" as query param Then the api response has a 200 Http status And the list of receipt is recovered from datastore and no receipt in the list has status "IO_ERROR_TO_NOTIFY" diff --git a/integration-test/src/step_definitions/biz_events_datastore_client.js b/integration-test/src/step_definitions/biz_events_datastore_client.js index 28fa3c7..c553f9c 100644 --- a/integration-test/src/step_definitions/biz_events_datastore_client.js +++ b/integration-test/src/step_definitions/biz_events_datastore_client.js @@ -17,8 +17,8 @@ async function getDocumentByIdFromBizEventsDatastore(id) { .fetchAll(); } -async function createDocumentInBizEventsDatastore(id, status) { - let event = createEvent(id, status); +async function createDocumentInBizEventsDatastore(id, status, orgCode, iuv) { + let event = createEvent(id, status, orgCode, iuv); try { return await container.items.create(event); } catch (err) { diff --git a/integration-test/src/step_definitions/common.js b/integration-test/src/step_definitions/common.js index 0221f72..99b2bd0 100644 --- a/integration-test/src/step_definitions/common.js +++ b/integration-test/src/step_definitions/common.js @@ -12,7 +12,7 @@ function getRandomInt(min, max) { } -function createEvent(id, status) { +function createEvent(id, status, orgCode, iuv) { let json_event = { "id": id, "version": "2", @@ -28,10 +28,10 @@ function createEvent(id, status) { "debtorPosition": { "modelType": "2", "noticeNumber": "310391366991197059", - "iuv": "intTestIuv" + "iuv": iuv || "iuv" }, "creditor": { - "idPA": "intTestOrgCode", + "idPA": orgCode || "orgCode", "idBrokerPA": "66666666666", "idStation": "66666666666_08", "companyName": "PA paolo", diff --git a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js index 1a664a1..62d3704 100644 --- a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js +++ b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js @@ -11,6 +11,7 @@ const { deleteDocumentFromReceiptErrorDatastore, getDocumentFromReceiptsErrorDatastoreByBizEventId, getDocumentFromReceiptsDatastoreByEventId, + deleteMultipleDocumentFromReceiptErrorDatastoreByEventId, deleteAllTestReceipts, deleteAllTestReceiptsError } = require("./receipts_datastore_client"); @@ -36,7 +37,6 @@ let eventId = null; let responseAPI = null; let receipt = null; let receiptError = null; -let event = null; let receiptPdfFileName = null; let listOfReceipts = []; @@ -51,16 +51,14 @@ After(async function () { } if (receiptPdfFileName != null) { await deleteBlob(receiptPdfFileName); - fs.unlinkSync(receiptPdfFileName); + if(fs.existsSync(receiptPdfFileName)){ + fs.unlinkSync(receiptPdfFileName); + } } if(listOfReceipts.length > 0){ for(let receipt of listOfReceipts){ await deleteDocumentFromReceiptsDatastore(receipt.id); - } - } - if(listOfBizEvents.length > 0){ - for(let bizEvent of listOfBizEvents){ - await deleteDocumentFromBizEventsDatastore(bizEvent.id); + await deleteDocumentFromBizEventsDatastore(receipt.eventId); } } @@ -68,7 +66,6 @@ After(async function () { responseAPI = null; receipt = null; receiptError = null; - event = null; receiptPdfFileName = null; listOfReceipts = []; }); @@ -92,6 +89,15 @@ Given('a receipt with eventId {string} and status {string} stored into receipt d assert.strictEqual(receiptsStoreResponse.statusCode, 201); }); +Given('a biz event with id {string} and status {string} and organizationFiscalCode {string} and IUV {string} stored on biz-events datastore', async function (id, status, orgCode, iuv) { + eventId = id; + // prior cancellation to avoid dirty cases + await deleteDocumentFromBizEventsDatastore(eventId); + + let bizEventStoreResponse = await createDocumentInBizEventsDatastore(id, status, orgCode, iuv); + assert.strictEqual(bizEventStoreResponse.statusCode, 201); + }); + Given('a receipt-error with bizEventId {string} and status {string} stored into receipt-error datastore', async function (id, status) { eventId = id; // prior cancellation to avoid dirty cases From f64462714a0dcc8aa3174564d73eb99f675a34fd Mon Sep 17 00:00:00 2001 From: svariant Date: Thu, 7 Dec 2023 17:24:43 +0100 Subject: [PATCH 15/38] [PRDP-242] Removed sleep --- .../src/step_definitions/receipt_pdf_helpdesk_step.js | 1 - 1 file changed, 1 deletion(-) diff --git a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js index 62d3704..a53f234 100644 --- a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js +++ b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js @@ -42,7 +42,6 @@ let listOfReceipts = []; // After each Scenario After(async function () { - sleep(10000); // remove event if (eventId != null) { await deleteDocumentFromBizEventsDatastore(eventId); From 17ab40b34ad89a989416bd3208f51367ea6a1438 Mon Sep 17 00:00:00 2001 From: svariant Date: Thu, 7 Dec 2023 18:28:37 +0100 Subject: [PATCH 16/38] [PRDP-242] Added teardown script to clean up after 5minutes from test --- integration-test/run_integration_test.sh | 7 +- integration-test/src/package.json | 4 + .../src/script/teardown_script.js | 109 ++++++++++++++++++ .../src/step_definitions/common.js | 1 + 4 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 integration-test/src/script/teardown_script.js diff --git a/integration-test/run_integration_test.sh b/integration-test/run_integration_test.sh index 750b584..3eef333 100644 --- a/integration-test/run_integration_test.sh +++ b/integration-test/run_integration_test.sh @@ -1,9 +1,10 @@ #!/bin/bash # example: sh ./run_integration_test.sh -set -e - # run integration tests cd ./src || exit yarn install -yarn test:"$1" \ No newline at end of file +yarn test:"$1" || true +echo "Sleeping for 300 seconds (5 minuts) before running teardown" +sleep 300 +yarn teardown:"$1" diff --git a/integration-test/src/package.json b/integration-test/src/package.json index 2f0d156..86023d8 100644 --- a/integration-test/src/package.json +++ b/integration-test/src/package.json @@ -7,6 +7,10 @@ "test:local": "dotenv -e ./config/.env.local yarn cucumber", "test:dev": "dotenv -e ./config/.env.dev yarn cucumber", "test:uat": "dotenv -e ./config/.env.uat yarn cucumber", + "teardown": "dotenv -e ./config/.env.local node script/teardown_script.js", + "teardown:local": "dotenv -e ./config/.env.local node script/teardown_script.js", + "teardown:dev": "dotenv -e ./config/.env.dev node script/teardown_script.js", + "teardown:uat": "dotenv -e ./config/.env.uat node script/teardown_script.js", "cucumber": "npx cucumber-js --publish -r step_definitions" }, "devDependencies": { diff --git a/integration-test/src/script/teardown_script.js b/integration-test/src/script/teardown_script.js new file mode 100644 index 0000000..00e926f --- /dev/null +++ b/integration-test/src/script/teardown_script.js @@ -0,0 +1,109 @@ +const { CosmosClient } = require("@azure/cosmos"); +const { BlobServiceClient, StorageSharedKeyCredential } = require('@azure/storage-blob'); +const { TOKENIZED_FISCAL_CODE } = require("../step_definitions/common"); + +//COSMOS RECEIPT +const cosmos_db_conn_string = process.env.RECEIPTS_COSMOS_CONN_STRING || ""; +const databaseId = process.env.RECEIPT_COSMOS_DB_NAME; +const receiptContainerId = process.env.RECEIPT_COSMOS_DB_CONTAINER_NAME; +const receiptErrorContainerId = process.env.RECEIPT_ERROR_COSMOS_DB_CONTAINER_NAME; + +const client = new CosmosClient(cosmos_db_conn_string); +const receiptContainer = client.database(databaseId).container(receiptContainerId); +const receiptErrorContainer = client.database(databaseId).container(receiptErrorContainerId); + +//COSMOS BIZEVENT +const biz_cosmos_db_conn_string = process.env.BIZEVENTS_COSMOS_CONN_STRING; +const bizDatabaseId = process.env.BIZ_EVENT_COSMOS_DB_NAME; // es. db +const bizContainerId = process.env.BIZ_EVENT_COSMOS_DB_CONTAINER_NAME; // es. biz-events + +const bizClient = new CosmosClient(biz_cosmos_db_conn_string); +const bizContainer = bizClient.database(bizDatabaseId).container(bizContainerId); + +//BLOB +const blobStorageContainerName = process.env.BLOB_STORAGE_CONTAINER_NAME; + +const accountName = process.env.BLOB_STORAGE_ACCOUNT_NAME; +if (!accountName) throw Error("Azure Storage accountName not found"); + +const accountKey = process.env.BLOB_STORAGE_ACCOUNT_KEY; +if (!accountKey) throw Error("Azure Storage accountKey not found"); + +const sharedKeyCredential = new StorageSharedKeyCredential( + accountName, + accountKey +); + +const blobServiceClient = new BlobServiceClient(`https://${accountName}.blob.core.windows.net`, sharedKeyCredential); +const containerClient = blobServiceClient.getContainerClient(blobStorageContainerName); + + +const deleteDocumentFromAllDatabases = async () => { + let { resources } = await receiptContainer.items.query({ + query: "SELECT * from c WHERE c.eventData.debtorFiscalCode = @fiscalCode", + parameters: [{ name: "@fiscalCode", value: TOKENIZED_FISCAL_CODE }] + }).fetchAll(); + + console.info(`Found n. ${resources?.length} receipts in the database`); + + for (const el of resources) { + console.log("Cleaning documents linked to receipts with id: " + el.id); + + //Delete PDF from Blob Storage + if (el?.mdAttach?.name?.length > 1) { + let response = await containerClient.getBlockBlobClient(el.mdAttach.name).deleteIfExists(); + if (response._response.status !== 202) { + console.error(`Error deleting PDF ${el.id}`); + } + console.log("RESPONSE DELETE PDF STATUS", response._response.status); + } + if (el?.mdAttachPayer?.name?.length > 1) { + let response = await containerClient.getBlockBlobClient(el.mdAttachPayer.name).deleteIfExists(); + if (response._response.status !== 202) { + console.error(`Error deleting PDF ${el.id}`); + } + console.log("RESPONSE DELETE PDF STATUS", response._response.status); + } + + //Delete Receipt from CosmosDB + try { + await receiptContainer.item(el.id, el.id).delete(); + } catch (error) { + if (error.code !== 404) { + console.error(`Error deleting receipt ${el.id}`); + } + } + + //Delete Receipt error from CosmosDB + try { + let response = await receiptErrorContainer.items.query({ + query: "SELECT * from c WHERE c.bizEventId = @bizEventId", + parameters: [{ name: "@bizEventId", value: el.eventId }] + }).fetchAll(); + + let resourcesError = response.resources; + + for (let error of resourcesError) { + await receiptErrorContainer.item(error.id, error.id).delete(); + } + } catch (error) { + if (error.code !== 404) { + console.error(`Error deleting receipt error ${el.eventId}`); + } + } + + //Delete BizEvent from CosmosDB + try { + await bizContainer.item(el.eventId, el.eventId).delete(); + } catch (error) { + if (error.code !== 404) { + console.error(`Error deleting bizevent ${el.eventId}`); + } + } + + console.log(`DONE ${el.id}`) + } + +}; + +deleteDocumentFromAllDatabases(); \ No newline at end of file diff --git a/integration-test/src/step_definitions/common.js b/integration-test/src/step_definitions/common.js index 99b2bd0..5aa7012 100644 --- a/integration-test/src/step_definitions/common.js +++ b/integration-test/src/step_definitions/common.js @@ -173,6 +173,7 @@ function createReceiptError(id, status) { } module.exports = { + TOKENIZED_FISCAL_CODE, createEvent, sleep, createReceipt, From 4a8cb8b0223a86d48aee860f180f501d769ad6fd Mon Sep 17 00:00:00 2001 From: svariant Date: Thu, 7 Dec 2023 18:30:06 +0100 Subject: [PATCH 17/38] [PRDP-242] Fix typo --- integration-test/run_integration_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-test/run_integration_test.sh b/integration-test/run_integration_test.sh index 3eef333..38208f8 100644 --- a/integration-test/run_integration_test.sh +++ b/integration-test/run_integration_test.sh @@ -5,6 +5,6 @@ cd ./src || exit yarn install yarn test:"$1" || true -echo "Sleeping for 300 seconds (5 minuts) before running teardown" +echo "Sleeping for 300 seconds (5 minutes) before running teardown" sleep 300 yarn teardown:"$1" From 1569f0c5d484e590e0427616a742423d98be85eb Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 11 Dec 2023 10:37:24 +0100 Subject: [PATCH 18/38] [PRDP-242] RecoverReceiptPdf integration test --- .../src/features/receipt_pdf_helpdesk.feature | 11 +++++- .../step_definitions/blob_storage_client.js | 13 ++++++- .../src/step_definitions/common.js | 6 +-- .../receipt_pdf_helpdesk_step.js | 39 ++++++++++++------- 4 files changed, 50 insertions(+), 19 deletions(-) diff --git a/integration-test/src/features/receipt_pdf_helpdesk.feature b/integration-test/src/features/receipt_pdf_helpdesk.feature index ef29405..66b5d2e 100644 --- a/integration-test/src/features/receipt_pdf_helpdesk.feature +++ b/integration-test/src/features/receipt_pdf_helpdesk.feature @@ -55,9 +55,18 @@ Feature: All about payment events to recover managed by Azure functions receipt- And the receipt with eventId "receipt-helpdesk-int-test-id-8" is recovered from datastore And the receipt has not status "IO_ERROR_TO_NOTIFY" -Scenario: recoverNotNotifiedReceiptMassive API retrieve all the receipts in status IO_ERROR_TO_NOTIFY and updates their status + Scenario: recoverNotNotifiedReceiptMassive API retrieve all the receipts in status IO_ERROR_TO_NOTIFY and updates their status Given a list of 5 receipts in status "IO_ERROR_TO_NOTIFY" stored into receipt datastore starting from eventId "receipt-helpdesk-int-test-id-9" And a list of 5 biz events in status "DONE" stored into biz-events datastore starting from eventId "receipt-helpdesk-int-test-id-9" When recoverNotNotifiedReceiptMassive API is called with status "IO_ERROR_TO_NOTIFY" as query param Then the api response has a 200 Http status And the list of receipt is recovered from datastore and no receipt in the list has status "IO_ERROR_TO_NOTIFY" + + Scenario: regenerateReceiptPdf API retrieve the receipt with the given eventId and regenerate its pdf updating receipt's metadata + Given a receipt with eventId "receipt-helpdesk-int-test-id-10" and status "IO_NOTIFIED" stored into receipt datastore + And a biz event with id "receipt-helpdesk-int-test-id-10" and status "DONE" stored on biz-events datastore + When recoverNotNotifiedReceiptMassive API is called with bizEventId "receipt-helpdesk-int-test-id-10" as query param + Then the api response has a 200 Http status + And the receipt with eventId "receipt-helpdesk-int-test-id-10" is recovered from datastore + And the receipt has attachment metadata + And the PDF is present on blob storage \ No newline at end of file diff --git a/integration-test/src/step_definitions/blob_storage_client.js b/integration-test/src/step_definitions/blob_storage_client.js index cd1dfdd..7edf5f3 100644 --- a/integration-test/src/step_definitions/blob_storage_client.js +++ b/integration-test/src/step_definitions/blob_storage_client.js @@ -42,7 +42,18 @@ async function deleteBlob(blobName) { await blockBlobClient.deleteIfExists(options); } +async function receiptPDFExist(blobName) { + let blobs = containerClient.listBlobsFlat(); + for await (const blob of blobs) { + if (blob.name === blobName) { + return true; + } + } + return false; +} + module.exports = { uploadBlobFromLocalPath, - deleteBlob + deleteBlob, + receiptPDFExist } \ No newline at end of file diff --git a/integration-test/src/step_definitions/common.js b/integration-test/src/step_definitions/common.js index 5aa7012..5a199b1 100644 --- a/integration-test/src/step_definitions/common.js +++ b/integration-test/src/step_definitions/common.js @@ -128,7 +128,7 @@ function createEvent(id, status, orgCode, iuv) { return json_event } -function createReceipt(id, status, pdfName) { +function createReceipt(id, status) { let receipt = { "eventId": id, @@ -147,8 +147,8 @@ function createReceipt(id, status, pdfName) { "numRetry": 0, "id": id } - if (status === "NOTIFIED" || status === "GENERATED") { - receipt.mdAttach = { "name": pdfName, "url": pdfName }; + if (status === "IO_NOTIFIED" || status === "GENERATED") { + receipt.mdAttach = { "name": "helpdesk-pdf-p.pdf", "url": "helpdesk-pdf-p.pdf" }; } return receipt } diff --git a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js index a53f234..2e6d535 100644 --- a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js +++ b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js @@ -2,7 +2,7 @@ const assert = require('assert'); const { After, Given, When, Then, setDefaultTimeout } = require('@cucumber/cucumber'); let fs = require('fs'); const { sleep } = require("./common"); -const { createDocumentInBizEventsDatastore, deleteDocumentFromBizEventsDatastore, deleteAllTestBizEvents } = require("./biz_events_datastore_client"); +const { createDocumentInBizEventsDatastore, deleteDocumentFromBizEventsDatastore } = require("./biz_events_datastore_client"); const { deleteDocumentFromReceiptsDatastore, createDocumentInReceiptsDatastore, @@ -11,9 +11,7 @@ const { deleteDocumentFromReceiptErrorDatastore, getDocumentFromReceiptsErrorDatastoreByBizEventId, getDocumentFromReceiptsDatastoreByEventId, - deleteMultipleDocumentFromReceiptErrorDatastoreByEventId, - deleteAllTestReceipts, - deleteAllTestReceiptsError + deleteMultipleDocumentFromReceiptErrorDatastoreByEventId } = require("./receipts_datastore_client"); const { getReceipt, @@ -27,7 +25,7 @@ const { postRecoverNotNotifiedReceiptMassive, postRegenerateReceiptPdf } = require("./api_helpdesk_client"); -const { uploadBlobFromLocalPath, deleteBlob } = require("./blob_storage_client"); +const { uploadBlobFromLocalPath, deleteBlob, receiptPDFExist } = require("./blob_storage_client"); // set timeout for Hooks function, it allows to wait for long task setDefaultTimeout(360 * 1000); @@ -79,15 +77,6 @@ Given('a biz event with id {string} and status {string} stored on biz-events dat assert.strictEqual(bizEventStoreResponse.statusCode, 201); }); -Given('a receipt with eventId {string} and status {string} stored into receipt datastore', async function (id, status) { - eventId = id; - // prior cancellation to avoid dirty cases - await deleteDocumentFromReceiptsDatastore(id); - - let receiptsStoreResponse = await createDocumentInReceiptsDatastore(id, status); - assert.strictEqual(receiptsStoreResponse.statusCode, 201); -}); - Given('a biz event with id {string} and status {string} and organizationFiscalCode {string} and IUV {string} stored on biz-events datastore', async function (id, status, orgCode, iuv) { eventId = id; // prior cancellation to avoid dirty cases @@ -97,6 +86,15 @@ Given('a biz event with id {string} and status {string} and organizationFiscalCo assert.strictEqual(bizEventStoreResponse.statusCode, 201); }); +Given('a receipt with eventId {string} and status {string} stored into receipt datastore', async function (id, status) { + eventId = id; + // prior cancellation to avoid dirty cases + await deleteDocumentFromReceiptsDatastore(id); + + let receiptsStoreResponse = await createDocumentInReceiptsDatastore(id, status); + assert.strictEqual(receiptsStoreResponse.statusCode, 201); +}); + Given('a receipt-error with bizEventId {string} and status {string} stored into receipt-error datastore', async function (id, status) { eventId = id; // prior cancellation to avoid dirty cases @@ -181,6 +179,10 @@ When("recoverNotNotifiedReceiptMassive API is called with status {string} as que responseAPI = await postRecoverNotNotifiedReceiptMassive(status); }); +When('recoverNotNotifiedReceiptMassive API is called with bizEventId {string} as query param', async function (id) { + responseAPI = await postRegenerateReceiptPdf(id); + }); + //Then Then('the api response has a {int} Http status', function (expectedStatus) { assert.strictEqual(responseAPI.status, expectedStatus); @@ -227,6 +229,15 @@ Then("the list of receipt is recovered from datastore and no receipt in the list } }); +Then('the receipt has attachment metadata', function () { + assert.strictEqual(receipt.mdAttachment != null || receipt.mdAttachment != "", true); +}); + +Then('the PDF is present on blob storage', async function () { + let blobExist = await receiptPDFExist(receiptPdfFileName); + assert.strictEqual(true, blobExist); + }); + Then("wait {int} ms", async function (milliSec) { sleep(milliSec) }); From 96a5f39a88a22dce2f233ab35b0eec29d03c232c Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 11 Dec 2023 10:53:10 +0100 Subject: [PATCH 19/38] [PRDP-242] Fix env docker int test --- docker/run_docker.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docker/run_docker.sh b/docker/run_docker.sh index 2c48890..e891409 100644 --- a/docker/run_docker.sh +++ b/docker/run_docker.sh @@ -26,9 +26,7 @@ if test -f "$FILE"; then rm .env fi config=$(yq -r '."microservice-chart".envConfig' ../helm/values-$ENV.yaml) -for line in $(echo $config | jq -r '. | to_entries[] | select(.key) | "\(.key)=\(.value)"'); do - echo $line >> .env -done +echo "$config" | jq -r '. | to_entries[] | select(.key) | "\(.key)=\(.value| @sh)"' >> .env keyvault=$(yq -r '."microservice-chart".keyvault.name' ../helm/values-$ENV.yaml) secret=$(yq -r '."microservice-chart".envSecret' ../helm/values-$ENV.yaml) From f9ba1474eb346ee20a37ebd6528f64f533f8f5b2 Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 11 Dec 2023 11:08:29 +0100 Subject: [PATCH 20/38] [PRDP-242] Moved int test teardown to github workflows --- .github/workflows/code_review.yml | 12 ++++++++++++ .github/workflows/integration_test.yml | 11 +++++++++++ integration-test/run_integration_test.sh | 8 ++++---- integration-test/run_teardown.sh | 5 +++++ 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 integration-test/run_teardown.sh diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index ee16c93..3e0a04f 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -86,6 +86,18 @@ jobs: chmod +x ./run_integration_test.sh ./run_integration_test.sh local + - name: Teardown integration tests + shell: bash + id: run_teardown + run: | + export CUCUMBER_PUBLISH_TOKEN=${{ secrets.CUCUMBER_PUBLISH_TOKEN }} + export RECEIPTS_COSMOS_CONN_STRING='${{ secrets.RECEIPTS_COSMOS_CONN_STRING }}' + export BIZEVENTS_COSMOS_CONN_STRING='${{ secrets.BIZEVENTS_COSMOS_CONN_STRING }}' + export SUBKEY='${{ secrets.SUBKEY }}' + cd ./integration-test + chmod +x ./run_teardown.sh + ./run_teardown.sh local + delete_github_deployments: runs-on: ubuntu-latest needs: smoke-test diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 9939387..71c764c 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -78,6 +78,17 @@ jobs: chmod +x ./run_integration_test.sh ./run_integration_test.sh ${{( github.event.inputs == null && 'uat') || inputs.environment }} + - name: Teardown integration tests + shell: bash + run: | + export CUCUMBER_PUBLISH_TOKEN=${{ secrets.CUCUMBER_PUBLISH_TOKEN }} + export RECEIPTS_COSMOS_CONN_STRING='${{ secrets.RECEIPTS_COSMOS_CONN_STRING }}' + export BIZEVENTS_COSMOS_CONN_STRING='${{ secrets.BIZEVENTS_COSMOS_CONN_STRING }}' + export SUBKEY='${{ secrets.SUBKEY }}' + cd ./integration-test + chmod +x ./run_teardown.sh + ./run_teardown.sh ${{( github.event.inputs == null && 'uat') || inputs.environment }} + notify: needs: [ create_runner, integration_test ] runs-on: [ self-hosted, "${{ needs.create_runner.outputs.runner_name }}" ] diff --git a/integration-test/run_integration_test.sh b/integration-test/run_integration_test.sh index 38208f8..0fa56be 100644 --- a/integration-test/run_integration_test.sh +++ b/integration-test/run_integration_test.sh @@ -1,10 +1,10 @@ #!/bin/bash # example: sh ./run_integration_test.sh +set -e + # run integration tests cd ./src || exit yarn install -yarn test:"$1" || true -echo "Sleeping for 300 seconds (5 minutes) before running teardown" -sleep 300 -yarn teardown:"$1" +yarn test:"$1" + diff --git a/integration-test/run_teardown.sh b/integration-test/run_teardown.sh new file mode 100644 index 0000000..f01d5ff --- /dev/null +++ b/integration-test/run_teardown.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +echo "Sleeping for 300 seconds (5 minutes) before running teardown" +sleep 300 +yarn teardown:"$1" \ No newline at end of file From 20ed12fc28f636df5c55217782e87718c6b2179e Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 11 Dec 2023 11:27:32 +0100 Subject: [PATCH 21/38] [PRDP-242] Moved teardown to own step --- .github/workflows/code_review.yml | 6 ++++++ .github/workflows/integration_test.yml | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 3e0a04f..9f10e72 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -86,6 +86,12 @@ jobs: chmod +x ./run_integration_test.sh ./run_integration_test.sh local + teardown: + name: Teardown + runs-on: ubuntu-latest + environment: + name: dev + steps: - name: Teardown integration tests shell: bash id: run_teardown diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 71c764c..3937612 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -78,6 +78,12 @@ jobs: chmod +x ./run_integration_test.sh ./run_integration_test.sh ${{( github.event.inputs == null && 'uat') || inputs.environment }} + teardown: + needs: [ create_runner, integration_test ] + name: Teardown ${{(github.event.inputs == null && 'uat') || inputs.environment }} + runs-on: [ self-hosted, "${{ needs.create_runner.outputs.runner_name }}" ] + environment: ${{(github.event.inputs == null && 'uat') || inputs.environment }} + steps: - name: Teardown integration tests shell: bash run: | @@ -144,7 +150,7 @@ jobs: cleanup_runner: name: Cleanup Runner - needs: [ create_runner, integration_test ] + needs: [ create_runner, integration_test, teardown ] if: ${{ always() }} runs-on: ubuntu-22.04 environment: ${{(github.event.inputs == null && 'uat') || inputs.environment }} From 252341ba7475b8dd4f41aca13030f12168331e00 Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 11 Dec 2023 11:29:32 +0100 Subject: [PATCH 22/38] [PRDP-242] Code review teardown condition --- .github/workflows/code_review.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 9f10e72..bd2f541 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -89,6 +89,7 @@ jobs: teardown: name: Teardown runs-on: ubuntu-latest + needs: smoke-test environment: name: dev steps: From 2731f8266cb054c612cdc45a87b0ac3fd06bece0 Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 11 Dec 2023 11:34:23 +0100 Subject: [PATCH 23/38] [PRDP-242] Added always condition to teardown --- .github/workflows/code_review.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index bd2f541..5c45922 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -90,6 +90,7 @@ jobs: name: Teardown runs-on: ubuntu-latest needs: smoke-test + if: ${{ always() }} environment: name: dev steps: From 02d27201f5f35595120502809e5c8217d1ddad04 Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 11 Dec 2023 11:40:21 +0100 Subject: [PATCH 24/38] [PRDP-242] Improved worflows conditions --- .github/workflows/code_review.yml | 2 +- .github/workflows/integration_test.yml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 5c45922..0b85501 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -108,7 +108,7 @@ jobs: delete_github_deployments: runs-on: ubuntu-latest - needs: smoke-test + needs: [smoke-test, teardown] if: ${{ always() }} steps: - name: Dump GitHub context diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 3937612..dd0c3c6 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -83,6 +83,7 @@ jobs: name: Teardown ${{(github.event.inputs == null && 'uat') || inputs.environment }} runs-on: [ self-hosted, "${{ needs.create_runner.outputs.runner_name }}" ] environment: ${{(github.event.inputs == null && 'uat') || inputs.environment }} + if: ${{ always() }} steps: - name: Teardown integration tests shell: bash @@ -116,7 +117,7 @@ jobs: delete_github_deployments: runs-on: ubuntu-latest - needs: integration_test + needs: [integration_test, teardown] if: ${{ always() }} steps: - name: Delete Previous deployments From 4d36670db085450bf65b2f14b6779d08c614ca6f Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 11 Dec 2023 11:48:19 +0100 Subject: [PATCH 25/38] [PRDP-242] Added login&checkout steps to workflows --- .github/workflows/code_review.yml | 11 +++++++++++ .github/workflows/integration_test.yml | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 0b85501..ba0d548 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -94,6 +94,17 @@ jobs: environment: name: dev steps: + - name: Checkout + id: checkout + uses: actions/checkout@1f9a0c22da41e6ebfa534300ef656657ea2c6707 + - name: Login + id: login + # from https://github.com/Azure/login/commits/master + uses: azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 + with: + client-id: ${{ secrets.CLIENT_ID }} + tenant-id: ${{ secrets.TENANT_ID }} + subscription-id: ${{ secrets.SUBSCRIPTION_ID }} - name: Teardown integration tests shell: bash id: run_teardown diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index dd0c3c6..fb71965 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -85,6 +85,18 @@ jobs: environment: ${{(github.event.inputs == null && 'uat') || inputs.environment }} if: ${{ always() }} steps: + - name: Checkout + id: checkout + uses: actions/checkout@1f9a0c22da41e6ebfa534300ef656657ea2c6707 + + - name: Login + id: login + # from https://github.com/Azure/login/commits/master + uses: azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 + with: + client-id: ${{ secrets.CLIENT_ID }} + tenant-id: ${{ secrets.TENANT_ID }} + subscription-id: ${{ secrets.SUBSCRIPTION_ID }} - name: Teardown integration tests shell: bash run: | From cfc3b1bec44c553a487b37130ba1a7e2883653ac Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 11 Dec 2023 11:52:48 +0100 Subject: [PATCH 26/38] [PRDP-242] Updated helpdesk url int test local --- integration-test/src/config/.env.local | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-test/src/config/.env.local b/integration-test/src/config/.env.local index b8618c8..674c696 100644 --- a/integration-test/src/config/.env.local +++ b/integration-test/src/config/.env.local @@ -13,4 +13,4 @@ BLOB_STORAGE_CONTAINER_NAME=pagopa-d-weu-receipts-azure-blob-receipt-st-attach ENVIRONMENT=local -HELPDESK_URL=http://localhost:53488/ \ No newline at end of file +HELPDESK_URL=http://localhost:60486/ \ No newline at end of file From b0da4f67db34dab8a3aedde7c284ecd5a73ff8a5 Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 11 Dec 2023 11:57:18 +0100 Subject: [PATCH 27/38] [PRDP-242] Fixed teardown shell --- integration-test/run_teardown.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/integration-test/run_teardown.sh b/integration-test/run_teardown.sh index f01d5ff..cbd46c3 100644 --- a/integration-test/run_teardown.sh +++ b/integration-test/run_teardown.sh @@ -2,4 +2,7 @@ echo "Sleeping for 300 seconds (5 minutes) before running teardown" sleep 300 + +cd ./src || exit +yarn install yarn teardown:"$1" \ No newline at end of file From 3813b506f62543faebd9fc106b3482cb2fcf7b49 Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 11 Dec 2023 12:20:59 +0100 Subject: [PATCH 28/38] [PRDP-242] Changed blobStorage client connection --- integration-test/src/config/.env.dev | 3 +-- integration-test/src/config/.env.local | 3 +-- integration-test/src/config/.env.uat | 3 +-- .../src/script/teardown_script.js | 16 +++----------- .../step_definitions/blob_storage_client.js | 21 ++++--------------- 5 files changed, 10 insertions(+), 36 deletions(-) diff --git a/integration-test/src/config/.env.dev b/integration-test/src/config/.env.dev index 84f5772..e03560a 100644 --- a/integration-test/src/config/.env.dev +++ b/integration-test/src/config/.env.dev @@ -7,8 +7,7 @@ RECEIPT_COSMOS_DB_NAME=db RECEIPT_COSMOS_DB_CONTAINER_NAME=receipts RECEIPT_ERROR_COSMOS_DB_CONTAINER_NAME=receipts-message-errors -BLOB_STORAGE_ACCOUNT_KEY= -BLOB_STORAGE_ACCOUNT_NAME=pagopadweureceiptsfnsa +BLOB_STORAGE_CONN_STRING= BLOB_STORAGE_CONTAINER_NAME=pagopa-d-weu-receipts-azure-blob-receipt-st-attach ENVIRONMENT=dev diff --git a/integration-test/src/config/.env.local b/integration-test/src/config/.env.local index 674c696..c6fab31 100644 --- a/integration-test/src/config/.env.local +++ b/integration-test/src/config/.env.local @@ -7,8 +7,7 @@ RECEIPT_COSMOS_DB_NAME=db RECEIPT_COSMOS_DB_CONTAINER_NAME=receipts RECEIPT_ERROR_COSMOS_DB_CONTAINER_NAME=receipts-message-errors -BLOB_STORAGE_ACCOUNT_KEY= -BLOB_STORAGE_ACCOUNT_NAME=pagopadweureceiptsfnsa +BLOB_STORAGE_CONN_STRING= BLOB_STORAGE_CONTAINER_NAME=pagopa-d-weu-receipts-azure-blob-receipt-st-attach ENVIRONMENT=local diff --git a/integration-test/src/config/.env.uat b/integration-test/src/config/.env.uat index e568853..54f0d3e 100644 --- a/integration-test/src/config/.env.uat +++ b/integration-test/src/config/.env.uat @@ -7,8 +7,7 @@ RECEIPT_COSMOS_DB_NAME=db RECEIPT_COSMOS_DB_CONTAINER_NAME=receipts RECEIPT_ERROR_COSMOS_DB_CONTAINER_NAME=receipts-message-errors -BLOB_STORAGE_ACCOUNT_KEY= -BLOB_STORAGE_ACCOUNT_NAME=pagopauweureceiptsfnsa +BLOB_STORAGE_CONN_STRING= BLOB_STORAGE_CONTAINER_NAME=pagopa-u-weu-receipts-azure-blob-receipt-st-attach ENVIRONMENT=uat diff --git a/integration-test/src/script/teardown_script.js b/integration-test/src/script/teardown_script.js index 00e926f..bce6ea8 100644 --- a/integration-test/src/script/teardown_script.js +++ b/integration-test/src/script/teardown_script.js @@ -21,21 +21,11 @@ const bizClient = new CosmosClient(biz_cosmos_db_conn_string); const bizContainer = bizClient.database(bizDatabaseId).container(bizContainerId); //BLOB +const blobStorageConnString = process.env.BLOB_STORAGE_CONN_STRING; const blobStorageContainerName = process.env.BLOB_STORAGE_CONTAINER_NAME; -const accountName = process.env.BLOB_STORAGE_ACCOUNT_NAME; -if (!accountName) throw Error("Azure Storage accountName not found"); - -const accountKey = process.env.BLOB_STORAGE_ACCOUNT_KEY; -if (!accountKey) throw Error("Azure Storage accountKey not found"); - -const sharedKeyCredential = new StorageSharedKeyCredential( - accountName, - accountKey -); - -const blobServiceClient = new BlobServiceClient(`https://${accountName}.blob.core.windows.net`, sharedKeyCredential); -const containerClient = blobServiceClient.getContainerClient(blobStorageContainerName); +const blobServiceClient = BlobServiceClient.fromConnectionString(blobStorageConnString || ""); +const containerClient = blobServiceClient.getContainerClient(blobStorageContainerName || ""); const deleteDocumentFromAllDatabases = async () => { diff --git a/integration-test/src/step_definitions/blob_storage_client.js b/integration-test/src/step_definitions/blob_storage_client.js index 7edf5f3..4108355 100644 --- a/integration-test/src/step_definitions/blob_storage_client.js +++ b/integration-test/src/step_definitions/blob_storage_client.js @@ -1,23 +1,10 @@ -const { BlobServiceClient, StorageSharedKeyCredential } = require('@azure/storage-blob'); +const { BlobServiceClient } = require('@azure/storage-blob'); +const blobStorageConnString = process.env.BLOB_STORAGE_CONN_STRING; const blobStorageContainerName = process.env.BLOB_STORAGE_CONTAINER_NAME; -// Azure Storage resource name -const accountName = process.env.BLOB_STORAGE_ACCOUNT_NAME; -if (!accountName) throw Error("Azure Storage accountName not found"); - -// Azure Storage resource key -const accountKey = process.env.BLOB_STORAGE_ACCOUNT_KEY; -if (!accountKey) throw Error("Azure Storage accountKey not found"); - -// Create credential -const sharedKeyCredential = new StorageSharedKeyCredential( - accountName, - accountKey -); - -const blobServiceClient = new BlobServiceClient(`https://${accountName}.blob.core.windows.net`, sharedKeyCredential); -const containerClient = blobServiceClient.getContainerClient(blobStorageContainerName); +const blobServiceClient = BlobServiceClient.fromConnectionString(blobStorageConnString || ""); +const containerClient = blobServiceClient.getContainerClient(blobStorageContainerName || ""); async function uploadBlobFromLocalPath(fileName, localFilePath) { const blobClient = containerClient.getBlockBlobClient(fileName); From db2d7611abf1338c4fdf4953a20ecc657e8170dc Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 11 Dec 2023 12:26:21 +0100 Subject: [PATCH 29/38] [PRDP-242] Changed storage conn string env --- integration-test/src/config/.env.dev | 2 +- integration-test/src/config/.env.local | 2 +- integration-test/src/config/.env.uat | 2 +- integration-test/src/script/teardown_script.js | 2 +- integration-test/src/step_definitions/blob_storage_client.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-test/src/config/.env.dev b/integration-test/src/config/.env.dev index e03560a..fa03fc4 100644 --- a/integration-test/src/config/.env.dev +++ b/integration-test/src/config/.env.dev @@ -7,7 +7,7 @@ RECEIPT_COSMOS_DB_NAME=db RECEIPT_COSMOS_DB_CONTAINER_NAME=receipts RECEIPT_ERROR_COSMOS_DB_CONTAINER_NAME=receipts-message-errors -BLOB_STORAGE_CONN_STRING= +RECEIPTS_STORAGE_CONN_STRING= BLOB_STORAGE_CONTAINER_NAME=pagopa-d-weu-receipts-azure-blob-receipt-st-attach ENVIRONMENT=dev diff --git a/integration-test/src/config/.env.local b/integration-test/src/config/.env.local index c6fab31..668c55a 100644 --- a/integration-test/src/config/.env.local +++ b/integration-test/src/config/.env.local @@ -7,7 +7,7 @@ RECEIPT_COSMOS_DB_NAME=db RECEIPT_COSMOS_DB_CONTAINER_NAME=receipts RECEIPT_ERROR_COSMOS_DB_CONTAINER_NAME=receipts-message-errors -BLOB_STORAGE_CONN_STRING= +RECEIPTS_STORAGE_CONN_STRING= BLOB_STORAGE_CONTAINER_NAME=pagopa-d-weu-receipts-azure-blob-receipt-st-attach ENVIRONMENT=local diff --git a/integration-test/src/config/.env.uat b/integration-test/src/config/.env.uat index 54f0d3e..72b8ffe 100644 --- a/integration-test/src/config/.env.uat +++ b/integration-test/src/config/.env.uat @@ -7,7 +7,7 @@ RECEIPT_COSMOS_DB_NAME=db RECEIPT_COSMOS_DB_CONTAINER_NAME=receipts RECEIPT_ERROR_COSMOS_DB_CONTAINER_NAME=receipts-message-errors -BLOB_STORAGE_CONN_STRING= +RECEIPTS_STORAGE_CONN_STRING= BLOB_STORAGE_CONTAINER_NAME=pagopa-u-weu-receipts-azure-blob-receipt-st-attach ENVIRONMENT=uat diff --git a/integration-test/src/script/teardown_script.js b/integration-test/src/script/teardown_script.js index bce6ea8..2605205 100644 --- a/integration-test/src/script/teardown_script.js +++ b/integration-test/src/script/teardown_script.js @@ -21,7 +21,7 @@ const bizClient = new CosmosClient(biz_cosmos_db_conn_string); const bizContainer = bizClient.database(bizDatabaseId).container(bizContainerId); //BLOB -const blobStorageConnString = process.env.BLOB_STORAGE_CONN_STRING; +const blobStorageConnString = process.env.RECEIPTS_STORAGE_CONN_STRING; const blobStorageContainerName = process.env.BLOB_STORAGE_CONTAINER_NAME; const blobServiceClient = BlobServiceClient.fromConnectionString(blobStorageConnString || ""); diff --git a/integration-test/src/step_definitions/blob_storage_client.js b/integration-test/src/step_definitions/blob_storage_client.js index 4108355..dacf864 100644 --- a/integration-test/src/step_definitions/blob_storage_client.js +++ b/integration-test/src/step_definitions/blob_storage_client.js @@ -1,6 +1,6 @@ const { BlobServiceClient } = require('@azure/storage-blob'); -const blobStorageConnString = process.env.BLOB_STORAGE_CONN_STRING; +const blobStorageConnString = process.env.RECEIPTS_STORAGE_CONN_STRING; const blobStorageContainerName = process.env.BLOB_STORAGE_CONTAINER_NAME; const blobServiceClient = BlobServiceClient.fromConnectionString(blobStorageConnString || ""); From 9d26032f800d6bd183de7e20a48e5dbd0444edb3 Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 11 Dec 2023 12:27:39 +0100 Subject: [PATCH 30/38] [PRDP-242] Modified teardown step --- .github/workflows/code_review.yml | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index ba0d548..bd73564 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -81,37 +81,21 @@ jobs: export CUCUMBER_PUBLISH_TOKEN=${{ secrets.CUCUMBER_PUBLISH_TOKEN }} export RECEIPTS_COSMOS_CONN_STRING='${{ secrets.RECEIPTS_COSMOS_CONN_STRING }}' export BIZEVENTS_COSMOS_CONN_STRING='${{ secrets.BIZEVENTS_COSMOS_CONN_STRING }}' + export RECEIPTS_STORAGE_CONN_STRING='${{ secrets.RECEIPTS_STORAGE_CONN_STRING }}' export SUBKEY='${{ secrets.SUBKEY }}' cd ./integration-test chmod +x ./run_integration_test.sh ./run_integration_test.sh local - teardown: - name: Teardown - runs-on: ubuntu-latest - needs: smoke-test - if: ${{ always() }} - environment: - name: dev - steps: - - name: Checkout - id: checkout - uses: actions/checkout@1f9a0c22da41e6ebfa534300ef656657ea2c6707 - - name: Login - id: login - # from https://github.com/Azure/login/commits/master - uses: azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 - with: - client-id: ${{ secrets.CLIENT_ID }} - tenant-id: ${{ secrets.TENANT_ID }} - subscription-id: ${{ secrets.SUBSCRIPTION_ID }} - name: Teardown integration tests shell: bash id: run_teardown + if: ${{ always() }} run: | export CUCUMBER_PUBLISH_TOKEN=${{ secrets.CUCUMBER_PUBLISH_TOKEN }} export RECEIPTS_COSMOS_CONN_STRING='${{ secrets.RECEIPTS_COSMOS_CONN_STRING }}' export BIZEVENTS_COSMOS_CONN_STRING='${{ secrets.BIZEVENTS_COSMOS_CONN_STRING }}' + export RECEIPTS_STORAGE_CONN_STRING='${{ secrets.RECEIPTS_STORAGE_CONN_STRING }}' export SUBKEY='${{ secrets.SUBKEY }}' cd ./integration-test chmod +x ./run_teardown.sh @@ -119,7 +103,7 @@ jobs: delete_github_deployments: runs-on: ubuntu-latest - needs: [smoke-test, teardown] + needs: smoke-test if: ${{ always() }} steps: - name: Dump GitHub context From 31b6e1f1a5466500c83be7ef06b9e29a8a57035f Mon Sep 17 00:00:00 2001 From: svariant Date: Mon, 11 Dec 2023 12:55:06 +0100 Subject: [PATCH 31/38] [PRDP-242] Added storage conn string to identity --- .identity/03_github_environment.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.identity/03_github_environment.tf b/.identity/03_github_environment.tf index 69a2d1c..93b74b0 100644 --- a/.identity/03_github_environment.tf +++ b/.identity/03_github_environment.tf @@ -26,7 +26,8 @@ locals { "SUBSCRIPTION_ID" : data.azurerm_subscription.current.subscription_id, "SUBKEY" : data.azurerm_key_vault_secret.key_vault_integration_test_subkey.value, "RECEIPTS_COSMOS_CONN_STRING" : "AccountEndpoint=https://pagopa-${var.env_short}-${local.location_short}-${local.domain}-ds-cosmos-account.documents.azure.com:443/;AccountKey=${data.azurerm_cosmosdb_account.receipts_cosmos.primary_key};", - "BIZEVENTS_COSMOS_CONN_STRING" : "AccountEndpoint=https://pagopa-${var.env_short}-${local.location_short}-bizevents-ds-cosmos-account.documents.azure.com:443/;AccountKey=${data.azurerm_cosmosdb_account.bizevents_cosmos.primary_key};" + "BIZEVENTS_COSMOS_CONN_STRING" : "AccountEndpoint=https://pagopa-${var.env_short}-${local.location_short}-bizevents-ds-cosmos-account.documents.azure.com:443/;AccountKey=${data.azurerm_cosmosdb_account.bizevents_cosmos.primary_key};", + "RECEIPTS_STORAGE_CONN_STRING" : data.azurerm_storage_account.receipts_sa.primary_connection_string, } env_variables = { "CONTAINER_APP_ENVIRONMENT_NAME" : local.container_app_environment.name, From dc04f89f1d689e8f8c156c59bab884a431b9497b Mon Sep 17 00:00:00 2001 From: svariant Date: Tue, 12 Dec 2023 09:20:25 +0100 Subject: [PATCH 32/38] [PRDP-242] Moved teardown step in integration test action --- .github/workflows/integration_test.yml | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index fb71965..b98e26b 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -78,27 +78,9 @@ jobs: chmod +x ./run_integration_test.sh ./run_integration_test.sh ${{( github.event.inputs == null && 'uat') || inputs.environment }} - teardown: - needs: [ create_runner, integration_test ] - name: Teardown ${{(github.event.inputs == null && 'uat') || inputs.environment }} - runs-on: [ self-hosted, "${{ needs.create_runner.outputs.runner_name }}" ] - environment: ${{(github.event.inputs == null && 'uat') || inputs.environment }} - if: ${{ always() }} - steps: - - name: Checkout - id: checkout - uses: actions/checkout@1f9a0c22da41e6ebfa534300ef656657ea2c6707 - - - name: Login - id: login - # from https://github.com/Azure/login/commits/master - uses: azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 - with: - client-id: ${{ secrets.CLIENT_ID }} - tenant-id: ${{ secrets.TENANT_ID }} - subscription-id: ${{ secrets.SUBSCRIPTION_ID }} - name: Teardown integration tests shell: bash + if: ${{ always() }} run: | export CUCUMBER_PUBLISH_TOKEN=${{ secrets.CUCUMBER_PUBLISH_TOKEN }} export RECEIPTS_COSMOS_CONN_STRING='${{ secrets.RECEIPTS_COSMOS_CONN_STRING }}' @@ -129,7 +111,7 @@ jobs: delete_github_deployments: runs-on: ubuntu-latest - needs: [integration_test, teardown] + needs: [integration_test] if: ${{ always() }} steps: - name: Delete Previous deployments @@ -163,7 +145,7 @@ jobs: cleanup_runner: name: Cleanup Runner - needs: [ create_runner, integration_test, teardown ] + needs: [ create_runner, integration_test ] if: ${{ always() }} runs-on: ubuntu-22.04 environment: ${{(github.event.inputs == null && 'uat') || inputs.environment }} From 4304c36567dc46b2baa12097318bf07d547a670c Mon Sep 17 00:00:00 2001 From: svariant Date: Tue, 12 Dec 2023 10:32:12 +0100 Subject: [PATCH 33/38] [PRDP-242] Lowered sleep --- integration-test/run_teardown.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-test/run_teardown.sh b/integration-test/run_teardown.sh index cbd46c3..ea6bddf 100644 --- a/integration-test/run_teardown.sh +++ b/integration-test/run_teardown.sh @@ -1,7 +1,7 @@ #!/bin/bash -echo "Sleeping for 300 seconds (5 minutes) before running teardown" -sleep 300 +echo "Sleeping for 120 seconds (2 minutes) before running teardown" +sleep 120 cd ./src || exit yarn install From efff054cfbcaf5a68cbc92120eb5fa204046585c Mon Sep 17 00:00:00 2001 From: svariant Date: Tue, 12 Dec 2023 10:41:36 +0100 Subject: [PATCH 34/38] [PRDP-242] Commented teardown --- .github/workflows/code_review.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index bd73564..84a35bc 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -87,19 +87,19 @@ jobs: chmod +x ./run_integration_test.sh ./run_integration_test.sh local - - name: Teardown integration tests - shell: bash - id: run_teardown - if: ${{ always() }} - run: | - export CUCUMBER_PUBLISH_TOKEN=${{ secrets.CUCUMBER_PUBLISH_TOKEN }} - export RECEIPTS_COSMOS_CONN_STRING='${{ secrets.RECEIPTS_COSMOS_CONN_STRING }}' - export BIZEVENTS_COSMOS_CONN_STRING='${{ secrets.BIZEVENTS_COSMOS_CONN_STRING }}' - export RECEIPTS_STORAGE_CONN_STRING='${{ secrets.RECEIPTS_STORAGE_CONN_STRING }}' - export SUBKEY='${{ secrets.SUBKEY }}' - cd ./integration-test - chmod +x ./run_teardown.sh - ./run_teardown.sh local + # - name: Teardown integration tests + # shell: bash + # id: run_teardown + # if: ${{ always() }} + # run: | + # export CUCUMBER_PUBLISH_TOKEN=${{ secrets.CUCUMBER_PUBLISH_TOKEN }} + # export RECEIPTS_COSMOS_CONN_STRING='${{ secrets.RECEIPTS_COSMOS_CONN_STRING }}' + # export BIZEVENTS_COSMOS_CONN_STRING='${{ secrets.BIZEVENTS_COSMOS_CONN_STRING }}' + # export RECEIPTS_STORAGE_CONN_STRING='${{ secrets.RECEIPTS_STORAGE_CONN_STRING }}' + # export SUBKEY='${{ secrets.SUBKEY }}' + # cd ./integration-test + # chmod +x ./run_teardown.sh + # ./run_teardown.sh local delete_github_deployments: runs-on: ubuntu-latest From 659a8c28dc1d592301bfc16cf1f39e46f10b6ce8 Mon Sep 17 00:00:00 2001 From: svariant Date: Tue, 12 Dec 2023 11:10:43 +0100 Subject: [PATCH 35/38] [PRDP-242] Fix feature step regenerate description --- integration-test/src/features/receipt_pdf_helpdesk.feature | 2 +- .../src/step_definitions/receipt_pdf_helpdesk_step.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-test/src/features/receipt_pdf_helpdesk.feature b/integration-test/src/features/receipt_pdf_helpdesk.feature index 66b5d2e..51b64e4 100644 --- a/integration-test/src/features/receipt_pdf_helpdesk.feature +++ b/integration-test/src/features/receipt_pdf_helpdesk.feature @@ -65,7 +65,7 @@ Feature: All about payment events to recover managed by Azure functions receipt- Scenario: regenerateReceiptPdf API retrieve the receipt with the given eventId and regenerate its pdf updating receipt's metadata Given a receipt with eventId "receipt-helpdesk-int-test-id-10" and status "IO_NOTIFIED" stored into receipt datastore And a biz event with id "receipt-helpdesk-int-test-id-10" and status "DONE" stored on biz-events datastore - When recoverNotNotifiedReceiptMassive API is called with bizEventId "receipt-helpdesk-int-test-id-10" as query param + When regenerateReceiptPdf API is called with bizEventId "receipt-helpdesk-int-test-id-10" as query param Then the api response has a 200 Http status And the receipt with eventId "receipt-helpdesk-int-test-id-10" is recovered from datastore And the receipt has attachment metadata diff --git a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js index 2e6d535..2766fde 100644 --- a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js +++ b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js @@ -179,7 +179,7 @@ When("recoverNotNotifiedReceiptMassive API is called with status {string} as que responseAPI = await postRecoverNotNotifiedReceiptMassive(status); }); -When('recoverNotNotifiedReceiptMassive API is called with bizEventId {string} as query param', async function (id) { +When('regenerateReceiptPdf API is called with bizEventId {string} as query param', async function (id) { responseAPI = await postRegenerateReceiptPdf(id); }); From d80d48c430d998e29a38acfb206a4cbdc28a2a28 Mon Sep 17 00:00:00 2001 From: svariant Date: Tue, 12 Dec 2023 11:15:09 +0100 Subject: [PATCH 36/38] [PRDP-242] Added storage conn string to integration test action --- .github/workflows/code_review.yml | 25 ++++++++++++------------- .github/workflows/integration_test.yml | 3 ++- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 84a35bc..d70afad 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -87,19 +87,18 @@ jobs: chmod +x ./run_integration_test.sh ./run_integration_test.sh local - # - name: Teardown integration tests - # shell: bash - # id: run_teardown - # if: ${{ always() }} - # run: | - # export CUCUMBER_PUBLISH_TOKEN=${{ secrets.CUCUMBER_PUBLISH_TOKEN }} - # export RECEIPTS_COSMOS_CONN_STRING='${{ secrets.RECEIPTS_COSMOS_CONN_STRING }}' - # export BIZEVENTS_COSMOS_CONN_STRING='${{ secrets.BIZEVENTS_COSMOS_CONN_STRING }}' - # export RECEIPTS_STORAGE_CONN_STRING='${{ secrets.RECEIPTS_STORAGE_CONN_STRING }}' - # export SUBKEY='${{ secrets.SUBKEY }}' - # cd ./integration-test - # chmod +x ./run_teardown.sh - # ./run_teardown.sh local + - name: Teardown integration tests + shell: bash + id: run_teardown + if: ${{ always() }} + run: | + export CUCUMBER_PUBLISH_TOKEN=${{ secrets.CUCUMBER_PUBLISH_TOKEN }} + export RECEIPTS_COSMOS_CONN_STRING='${{ secrets.RECEIPTS_COSMOS_CONN_STRING }}' + export BIZEVENTS_COSMOS_CONN_STRING='${{ secrets.BIZEVENTS_COSMOS_CONN_STRING }}' + export RECEIPTS_STORAGE_CONN_STRING='${{ secrets.RECEIPTS_STORAGE_CONN_STRING }}' + cd ./integration-test + chmod +x ./run_teardown.sh + ./run_teardown.sh local delete_github_deployments: runs-on: ubuntu-latest diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index b98e26b..2c90285 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -73,6 +73,7 @@ jobs: export CUCUMBER_PUBLISH_TOKEN=${{ secrets.CUCUMBER_PUBLISH_TOKEN }} export RECEIPTS_COSMOS_CONN_STRING='${{ secrets.RECEIPTS_COSMOS_CONN_STRING }}' export BIZEVENTS_COSMOS_CONN_STRING='${{ secrets.BIZEVENTS_COSMOS_CONN_STRING }}' + export RECEIPTS_STORAGE_CONN_STRING='${{ secrets.RECEIPTS_STORAGE_CONN_STRING }}' export SUBKEY='${{ secrets.SUBKEY }}' cd ./integration-test chmod +x ./run_integration_test.sh @@ -85,7 +86,7 @@ jobs: export CUCUMBER_PUBLISH_TOKEN=${{ secrets.CUCUMBER_PUBLISH_TOKEN }} export RECEIPTS_COSMOS_CONN_STRING='${{ secrets.RECEIPTS_COSMOS_CONN_STRING }}' export BIZEVENTS_COSMOS_CONN_STRING='${{ secrets.BIZEVENTS_COSMOS_CONN_STRING }}' - export SUBKEY='${{ secrets.SUBKEY }}' + export RECEIPTS_STORAGE_CONN_STRING='${{ secrets.RECEIPTS_STORAGE_CONN_STRING }}' cd ./integration-test chmod +x ./run_teardown.sh ./run_teardown.sh ${{( github.event.inputs == null && 'uat') || inputs.environment }} From e54db86dcca1a67328fb4a44e77fe0b20c65389c Mon Sep 17 00:00:00 2001 From: svariant Date: Tue, 12 Dec 2023 15:26:59 +0100 Subject: [PATCH 37/38] [PRDP-242] Added template.zip --- src/main/resources/template.zip | Bin 0 -> 156278 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/resources/template.zip diff --git a/src/main/resources/template.zip b/src/main/resources/template.zip new file mode 100644 index 0000000000000000000000000000000000000000..ca34299a6d1b879bb33a8f69b861b5a3132c907b GIT binary patch literal 156278 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�dxDl@{{ziW$Sub*TdiXdNLxP7RCh}7Ygu`Zbm>G9O zw1efwFvKO4!N6QZe3YEHML}2iktm-qA3Gmso`9L2p2be(By@hg_-*S^N8-X*W2_f- z)74)v?;@TbD1XM`Sx_tbMoXmT-}A85PaH#{CZZG9Jt6jMi_)v5gi_yi*^fB>_gM!L z06cUjOO2D?I96X?SbP*+o=<#?LEiU9$yeS}T$K5`Y}E3Qf{@CtD_k*N5iw#$y^+%* zwg7QuIiE&#A}0(unb{ER`Xm8hh*(=S_}J7-kLxlXrxJunHEQpvAp;fHg03=%UD;Xm zq7|((lq(oCKM{vYlmhjta@WZyN_$)G+Y^7nJj6K$BY<2zxi7=GPU*UhkQ?ha9>{QS z54(sQ?*yb=!oS#+xT7}X))2pAbQDdx@bXM~EBMHBXM*1%q%8s;KOxH;Hb9mgNvIH~ zA=F5$){YutK^0d6#ZVs8ccd0wQTaf)8xD*Lt{(48CY+kYkRbWX zN#XV?M}Z5cM+IMmBIWV6y$JK+P_;10kmzI&YFYz@Ftvyn+l;e>0_>>HCQ=;5)^c!X zZnO^Bh$f+yo{dJJ9AsNx+nRo+DR@Dzl@+Nc;8N~oGwwg>S}_zl4Y(-e$*P#)~USviY|6D;d=eWUUk$H&X&v^i?r~Lu6jgLCs#xb z!X302d^qPJn*K**6R5!TjX-^~N0c3wIL;U7jUlY>QE;BG^DKfmi|JrnL@>zW%a_f! zZoWa!H_q3Vx7tx>>V6{@-*X0}5Y%`MlvwtO=wr$4~ zLE8iTq7fV<-)mn#>~4ef4xdM;`Yo}+Dc|gV#M=|$4JRW0DrgR7y%kvF*>|o=>{A@} znkz%F7MiB*4s;pf3~$S+4RItbnRYHwmNsGaZ(6TYnZY0y!sO5NzyU zH`sZ;zihQN5MZl2_Set}km?`Fwo40Y{(!MR30Oy-2>4b8{>fWRu` z0YTRiFMt8(#~$0INC=?#%QiX4$k
q#E#Mi7ME3M1*;cn&WBXI?l0G!mqGn#KnX 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

Jk;mzvG?5hVgS*B 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 eaj7=7vL}A#<>*$ literal 0 HcmV?d00001 From 8ccd3c24cd2c80d6b4dc5670a1f8756483eb338c Mon Sep 17 00:00:00 2001 From: svariant Date: Tue, 12 Dec 2023 15:47:16 +0100 Subject: [PATCH 38/38] [PRDP-242] Fixed filename extension + int test --- .../src/step_definitions/receipt_pdf_helpdesk_step.js | 5 +++-- .../pdf/helpdesk/client/impl/ReceiptBlobClientImpl.java | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js index 2766fde..5ea84c1 100644 --- a/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js +++ b/integration-test/src/step_definitions/receipt_pdf_helpdesk_step.js @@ -230,12 +230,13 @@ Then("the list of receipt is recovered from datastore and no receipt in the list }); Then('the receipt has attachment metadata', function () { - assert.strictEqual(receipt.mdAttachment != null || receipt.mdAttachment != "", true); + assert.strictEqual(receipt.mdAttach != undefined && receipt.mdAttach != null && receipt.mdAttach.name != "", true); + receiptPdfFileName = receipt.mdAttach.name; }); Then('the PDF is present on blob storage', async function () { let blobExist = await receiptPDFExist(receiptPdfFileName); - assert.strictEqual(true, blobExist); + assert.strictEqual(blobExist, true); }); Then("wait {int} ms", async function (milliSec) { diff --git a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/client/impl/ReceiptBlobClientImpl.java b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/client/impl/ReceiptBlobClientImpl.java index 367bba1..f53c671 100644 --- a/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/client/impl/ReceiptBlobClientImpl.java +++ b/src/main/java/it/gov/pagopa/receipt/pdf/helpdesk/client/impl/ReceiptBlobClientImpl.java @@ -76,10 +76,9 @@ public BlobStorageResponse savePdfToBlobStorage(InputStream pdf, String fileName //Create the container and return a container client object BlobContainerClient blobContainerClient = this.blobServiceClient.getBlobContainerClient(containerName); - String fileNamePdf = fileName + FILE_EXTENSION; //Get a reference to a blob - BlobClient blobClient = blobContainerClient.getBlobClient(fileNamePdf); + BlobClient blobClient = blobContainerClient.getBlobClient(fileName); //Upload the blob Response blockBlobItemResponse = blobClient.uploadWithResponse(