From 9a7286568a97f418b4cfaf523d9b9238f6b3dbfa Mon Sep 17 00:00:00 2001 From: Mitch-Budibase Date: Tue, 18 Jan 2022 16:02:42 +0000 Subject: [PATCH] Structural Changes + New Test Structural changes surrounding data sources -Cypress env file removed -Text change for fetching tables -Oracle tests organised -PostgreSQL tests organised New test is for skipping table fetch via button There are a few other smaller changes --- packages/builder/cypress.env.json | 10 --- .../integration/createUserAndRoles.spec.js | 4 +- .../datasources/datasourceWizard.spec.js | 2 +- .../integration/datasources/mySql.spec.js | 9 +-- .../integration/datasources/oracle.spec.js | 56 ++++++++----- .../datasources/postgreSql.spec.js | 81 ++++++++----------- .../queryLevelTransformers.spec.js | 3 +- packages/builder/cypress/support/commands.js | 27 ++++--- .../support/queryLevelTransformerFunction.js | 12 +-- .../queryLevelTransformerFunctionWithData.js | 20 ++--- packages/builder/package.json | 2 +- packages/builder/yarn.lock | 28 +++---- 12 files changed, 122 insertions(+), 132 deletions(-) delete mode 100644 packages/builder/cypress.env.json diff --git a/packages/builder/cypress.env.json b/packages/builder/cypress.env.json deleted file mode 100644 index a7ca217624..0000000000 --- a/packages/builder/cypress.env.json +++ /dev/null @@ -1,10 +0,0 @@ -{ -"HOST_IP": "", -"oracle": { - "HOST": "54.216.41.156", - "PORT": "1521", - "DATABASE": "xepdb1", - "USER": "hr", - "PASSWORD": "budiverse9k" - } -} diff --git a/packages/builder/cypress/integration/createUserAndRoles.spec.js b/packages/builder/cypress/integration/createUserAndRoles.spec.js index 0c8c32b224..538b910bb4 100644 --- a/packages/builder/cypress/integration/createUserAndRoles.spec.js +++ b/packages/builder/cypress/integration/createUserAndRoles.spec.js @@ -5,12 +5,13 @@ context("Create a User and Assign Roles", () => { it("should create a user", () => { cy.createUser("bbuser@test.com") - cy.contains("bbuser").should("be.visible") + cy.get(".spectrum-Table-body").should('contain', 'bbuser') }) it("should confirm there is No Access for a New User", () => { // Click into the user cy.contains("bbuser").click() + cy.wait(500) // Get No Access table - Confirm it has apps in it cy.get(".spectrum-Table").eq(1).should('not.contain', 'No rows found') // Get Configure Roles table - Confirm it has no apps @@ -38,6 +39,7 @@ context("Create a User and Assign Roles", () => { cy.wait(500) for (let i = 0; i < 3; i++) { cy.get(".spectrum-Table-body").eq(1).find('tr').eq(0).click() + cy.wait(500) cy.get(".spectrum-Dialog-grid").contains("Choose an option").click().then(() => { cy.wait(500) if (i == 0) { diff --git a/packages/builder/cypress/integration/datasources/datasourceWizard.spec.js b/packages/builder/cypress/integration/datasources/datasourceWizard.spec.js index 612a8b939a..5d8614bf01 100644 --- a/packages/builder/cypress/integration/datasources/datasourceWizard.spec.js +++ b/packages/builder/cypress/integration/datasources/datasourceWizard.spec.js @@ -28,7 +28,7 @@ context("Datasource Wizard", () => { // Modal will close and provide 500 error cy.intercept('**/datasources').as('datasourceConnection') cy.get(".spectrum-Dialog-grid").within(() => { - cy.get(".spectrum-Button").contains("Fetch tables from database").click({ force: true }) + cy.get(".spectrum-Button").contains("Save and fetch tables").click({ force: true }) }) cy.wait("@datasourceConnection") cy.get("@datasourceConnection").its('response.body') diff --git a/packages/builder/cypress/integration/datasources/mySql.spec.js b/packages/builder/cypress/integration/datasources/mySql.spec.js index 17d6bf169e..5cb579a447 100644 --- a/packages/builder/cypress/integration/datasources/mySql.spec.js +++ b/packages/builder/cypress/integration/datasources/mySql.spec.js @@ -15,7 +15,7 @@ context("MySQL Datasource Testing", () => { // Attempt to fetch tables without applying configuration cy.intercept('**/datasources').as('datasource') cy.get(".spectrum-Button") - .contains("Fetch tables from database") + .contains("Save and fetch tables") .click({ force: true }) // Intercept Request after button click & apply assertions cy.wait("@datasource") @@ -119,11 +119,8 @@ context("MySQL Datasource Testing", () => { }) cy.reload() } - // Table has placeholder tr when empty - cy.get(".spectrum-Table-body") - .eq(1) - .find('tr') - .should('have.length', 1) + // Confirm relationships no longer exist + cy.get(".spectrum-Body").should('contain', 'No relationships configured') }) }) diff --git a/packages/builder/cypress/integration/datasources/oracle.spec.js b/packages/builder/cypress/integration/datasources/oracle.spec.js index 5786ec0699..0a41ead4f3 100644 --- a/packages/builder/cypress/integration/datasources/oracle.spec.js +++ b/packages/builder/cypress/integration/datasources/oracle.spec.js @@ -7,13 +7,29 @@ context("Oracle Datasource Testing", () => { const queryName = "Cypress Test Query" const queryRename = "CT Query Rename" - it("Should add Oracle data source without configuration", () => { + it("Should add Oracle data source and skip table fetch", () => { + // Select Oracle data source + cy.selectExternalDatasource(datasource) + // Skip table fetch - no config added + cy.get(".spectrum-Button").contains("Skip table fetch").click({ force: true }) + cy.wait(500) + // Confirm config contains localhost + cy.get(".spectrum-Textfield-input").eq(1).should('have.value', 'localhost') + // Add another Oracle data source, configure & skip table fetch + cy.selectExternalDatasource(datasource) + cy.addDatasourceConfig(datasource, true) + // Confirm config and no tables + cy.get(".spectrum-Textfield-input").eq(1).should('have.value', Cypress.env("oracle").HOST) + cy.get(".spectrum-Body").eq(2).should('contain', 'No tables found.') + }) + + it("Should add Oracle data source and fetch tables without configuration", () => { // Select Oracle data source cy.selectExternalDatasource(datasource) // Attempt to fetch tables without applying configuration cy.intercept('**/datasources').as('datasource') cy.get(".spectrum-Button") - .contains("Fetch tables from database") + .contains("Save and fetch tables") .click({ force: true }) // Intercept Request after button click & apply assertions cy.wait("@datasource") @@ -109,11 +125,8 @@ context("Oracle Datasource Testing", () => { }) cy.reload() } - // Table has placeholder tr when empty - cy.get(".spectrum-Table-body") - .eq(1) - .find('tr') - .should('have.length', 1) + // Confirm relationships no longer exist + cy.get(".spectrum-Body").should('contain', 'No relationships configured') }) }) @@ -142,10 +155,10 @@ context("Oracle Datasource Testing", () => { }) it("should duplicate a query", () => { - // Get last nav item - The query - cy.get(".nav-item").last().within(() => { - cy.get(".icon").eq(1).click({ force: true }) - }) + // Get query nav item + cy.get(".nav-item").contains(queryName).parent().within(() => { + cy.get(".spectrum-Icon").eq(1).click({ force: true }) + }) // Select and confirm duplication cy.get(".spectrum-Menu").contains("Duplicate").click() cy.get(".nav-item").should('contain', queryName + ' (1)') @@ -162,18 +175,17 @@ context("Oracle Datasource Testing", () => { }) it("should delete a query", () => { - // Get last nav item - The query - for (let i = 0; i < 2; i++) { - cy.get(".nav-item").last().within(() => { - cy.get(".icon").eq(1).click({ force: true }) - }) - // Select Delete - cy.get(".spectrum-Menu").contains("Delete").click() - cy.get(".spectrum-Button").contains("Delete Query").click({ force: true }) - cy.wait(1000) - } + // Get query nav item - QueryName + cy.get(".nav-item").contains(queryName).parent().within(() => { + cy.get(".spectrum-Icon").eq(1).click({ force: true }) + }) + + // Select Delete + cy.get(".spectrum-Menu").contains("Delete").click() + cy.get(".spectrum-Button").contains("Delete Query").click({ force: true }) + cy.wait(1000) + // Confirm deletion cy.get(".nav-item").should('not.contain', queryName) - cy.get(".nav-item").should('not.contain', queryRename) }) }) diff --git a/packages/builder/cypress/integration/datasources/postgreSql.spec.js b/packages/builder/cypress/integration/datasources/postgreSql.spec.js index b1dc929c33..4bd2f7c22f 100644 --- a/packages/builder/cypress/integration/datasources/postgreSql.spec.js +++ b/packages/builder/cypress/integration/datasources/postgreSql.spec.js @@ -15,7 +15,7 @@ context("PostgreSQL Datasource Testing", () => { // Attempt to fetch tables without applying configuration cy.intercept('**/datasources').as('datasource') cy.get(".spectrum-Button") - .contains("Fetch tables from database") + .contains("Save and fetch tables") .click({ force: true }) // Intercept Request after button click & apply assertions cy.wait("@datasource") @@ -96,6 +96,23 @@ context("PostgreSQL Datasource Testing", () => { .should('contain', "LOCATIONS through COUNTRIES → REGIONS") }) + it("should delete a relationship", () => { + cy.get(".hierarchy-items-container").contains(datasource).click() + cy.reload() + // Delete one relationship + cy.get(".spectrum-Table-body").eq(1).within(() => { + cy.get(".spectrum-Table-row").eq(0).click() + cy.wait(500) + }) + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button").contains("Delete").click({ force: true }) + }) + cy.reload() + // Confirm relationship was deleted + cy.get(".spectrum-Table-body") + .eq(1).find('tr').its('length').should('eq', 1) + }) + it("should add a query", () => { // Add query cy.get(".spectrum-Button").contains("Add query").click({ force: true }) @@ -125,39 +142,38 @@ context("PostgreSQL Datasource Testing", () => { cy.get(".hierarchy-items-container").contains(datasource).click() switchSchema("randomText") - // No tables displayed - No rows found message received - cy.get(".spectrum-Table-body").eq(1).should('contain', 'No rows found') + // No tables displayed + cy.get(".spectrum-Body").eq(2).should('contain', 'No tables found') - // Define relationship button should not be visible - cy.get(".spectrum-Button").should('not.contain', 'Define relationship') - // Relationship table should be empty - cy.get(".spectrum-Table-body").eq(2).should('contain', 'No rows found') - - // Query table should not exist - cy.get(".container").find(".spectrum-Table").should('have.length', 2) + // Previously created query should be visible + cy.get(".spectrum-Table-body").should('contain', queryName) }) it("should switch schemas", () => { // Switch schema - To one with tables switchSchema("1") - // Confirm tables exist - cy.get(".spectrum-Table-body").eq(0).should('not.contain', 'No rows found') - cy.get(".spectrum-Table-body").eq(0).find('tr').its('length').should('be.gt', 0) + // Confirm tables exist - Check for specific one + cy.get(".spectrum-Table-body").eq(0).should('contain', 'test') + cy.get(".spectrum-Table-body").eq(0).find('tr').its('length').should('eq', 1) - // Relationship table should be empty & query table should not exist - cy.get(".spectrum-Table-body").eq(2).should('contain', 'No rows found') - cy.get(".container").find(".spectrum-Table").should('have.length', 2) + // Confirm specific table visible within left nav bar + cy.get(".hierarchy-items-container").should('contain', 'test') // Switch back to public schema switchSchema("public") // Confirm tables exist - again - cy.get(".spectrum-Table-body").eq(1).should('not.contain', 'No rows found') - cy.get(".spectrum-Table-body").eq(1).find('tr').should('be.gt', 0) + cy.get(".spectrum-Table-body").eq(0).should('contain', 'REGIONS') + cy.get(".spectrum-Table-body").eq(0) + .find('tr').its('length').should('be.gt', 1) // Confirm specific table visible within left nav bar cy.get(".hierarchy-items-container").should('contain', 'REGIONS') + + // No relationships and one query + cy.get(".spectrum-Body").eq(3).should('contain', 'No relationships configured.') + cy.get(".spectrum-Table-body").eq(1).should('contain', queryName) }) it("should duplicate a query", () => { @@ -171,9 +187,6 @@ context("PostgreSQL Datasource Testing", () => { }) it("should edit a query name", () => { - // Ensure correct schema is selected - switchSchema("public") - // Access query cy.get(".hierarchy-items-container").contains(queryName + ' (1)').click() @@ -205,31 +218,6 @@ context("PostgreSQL Datasource Testing", () => { cy.get(".nav-item").should('not.contain', queryRename) }) - it("should delete relationships", () => { - cy.get(".hierarchy-items-container").contains(datasource).click() - cy.reload() - // Delete both relationships - cy.get(".spectrum-Table-body") - .eq(1).find('tr').its('length') - .then((len) => { - for (let i = 0; i < len; i++) { - cy.get(".spectrum-Table-body").eq(1).within(() => { - cy.get(".spectrum-Table-row").eq(0).click() - cy.wait(500) - }) - cy.get(".spectrum-Dialog-grid").within(() => { - cy.get(".spectrum-Button").contains("Delete").click({ force: true }) - }) - cy.reload() - } - // Table has placeholder tr when empty - cy.get(".spectrum-Table-body") - .eq(1) - .find('tr') - .should('have.length', 1) - }) - }) - const switchSchema = (schema) => { // Edit configuration - Change Schema cy.get(".spectrum-Textfield").eq(6).within(() => { @@ -243,6 +231,7 @@ context("PostgreSQL Datasource Testing", () => { cy.get(".spectrum-Button").contains("Fetch tables").click({ force: true }) }) cy.reload() + cy.wait(5000) } } }) diff --git a/packages/builder/cypress/integration/queryLevelTransformers.spec.js b/packages/builder/cypress/integration/queryLevelTransformers.spec.js index 526328c1be..018627a80c 100644 --- a/packages/builder/cypress/integration/queryLevelTransformers.spec.js +++ b/packages/builder/cypress/integration/queryLevelTransformers.spec.js @@ -1,7 +1,8 @@ context("Query Level Transformers", () => { before(() => { cy.login() - cy.createTestApp() + cy.deleteApp("Cypress Tests") + cy.createApp("Cypress Tests") }) it("should write a transformer function", () => { diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index 6bdebe6766..666025e635 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -343,8 +343,8 @@ Cypress.Commands.add("selectExternalDatasource", datasourceName => { }) }) -Cypress.Commands.add("addDatasourceConfig", (datasource, noFetch) => { - // addExternalDatasource should be called prior to this +Cypress.Commands.add("addDatasourceConfig", (datasource, skipFetch) => { + // selectExternalDatasource should be called prior to this // Adds the config for specified datasource & fetches tables // Currently supports MySQL, PostgreSQL, Oracle // Host IP Address @@ -369,14 +369,14 @@ Cypress.Commands.add("addDatasourceConfig", (datasource, noFetch) => { cy.get(".form-row") .eq(4) .within(() => { - cy.get("input").clear().type("mysql") + cy.get("input").clear().type(Cypress.env("mysql").DATABASE) }) } else { cy.get(".form-row") .eq(2) .within(() => { if (datasource == "PostgreSQL") { - cy.get("input").clear().type("test") + cy.get("input").clear().type(Cypress.env("postgresql").DATABASE) } if (datasource == "Oracle") { cy.get("input").clear().type(Cypress.env("oracle").DATABASE) @@ -390,14 +390,14 @@ Cypress.Commands.add("addDatasourceConfig", (datasource, noFetch) => { cy.get(".form-row") .eq(2) .within(() => { - cy.get("input").clear().type("root") + cy.get("input").clear().type(Cypress.env("mysql").USER) }) } else { cy.get(".form-row") .eq(3) .within(() => { if (datasource == "PostgreSQL") { - cy.get("input").clear().type("admin") + cy.get("input").clear().type(Cypress.env("postgresql").USER) } if (datasource == "Oracle") { cy.get("input").clear().type(Cypress.env("oracle").USER) @@ -411,14 +411,14 @@ Cypress.Commands.add("addDatasourceConfig", (datasource, noFetch) => { cy.get(".form-row") .eq(3) .within(() => { - cy.get("input").clear().type("abdc321d-4d21-4fc7-8d20-f40ab9fe6db0") + cy.get("input").clear().type(Cypress.env("mysql").PASSWORD) }) } else { cy.get(".form-row") .eq(4) .within(() => { if (datasource == "PostgreSQL") { - cy.get("input").clear().type("8cb2b6f4-4b33-4e86-b790-74eee608a4e9") + cy.get("input").clear().type(Cypress.env("postgresql").PASSWORD) } if (datasource == "Oracle") { cy.get("input").clear().type(Cypress.env("oracle").PASSWORD) @@ -427,10 +427,15 @@ Cypress.Commands.add("addDatasourceConfig", (datasource, noFetch) => { } }) // Click to fetch tables - if (!noFetch) { + if (skipFetch) { cy.get(".spectrum-Dialog-grid").within(() => { - cy.get(".spectrum-Button") - .contains("Fetch tables from database") + cy.get(".spectrum-Button").contains("Skip table fetch") + .click({ force: true }) + }) + } + else { + cy.get(".spectrum-Dialog-grid").within(() => { + cy.get(".spectrum-Button").contains("Save and fetch tables") .click({ force: true }) cy.wait(1000) }) diff --git a/packages/builder/cypress/support/queryLevelTransformerFunction.js b/packages/builder/cypress/support/queryLevelTransformerFunction.js index f77433fc4f..0c099df1a0 100644 --- a/packages/builder/cypress/support/queryLevelTransformerFunction.js +++ b/packages/builder/cypress/support/queryLevelTransformerFunction.js @@ -1,12 +1,12 @@ const breweries = data const totals = {} -for (let brewery of breweries) { - const state = brewery.state - if (totals[state] == null) { - totals[state] = 1 - } else { - totals[state]++ +for (let brewery of breweries) + {const state = brewery.state + if (totals[state] == null) + {totals[state] = 1 + } else + {totals[state]++ } } const entries = Object.entries(totals) diff --git a/packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js b/packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js index 3ca9f9c6fd..b87f95ade0 100644 --- a/packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js +++ b/packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js @@ -1,15 +1,15 @@ const breweries = data const totals = {} -for (let brewery of breweries) { - const state = brewery.state - if (totals[state] == null) { - totals[state] = 1 - } else { - totals[state]++ +for (let brewery of breweries) + {const state = brewery.state + if (totals[state] == null) + {totals[state] = 1 + } else + {totals[state]++ } } -const stateCodes = { - texas: "tx", +const stateCodes = + {texas: "tx", colorado: "co", florida: "fl", iwoa: "ia", @@ -24,7 +24,7 @@ const stateCodes = { ohio: "oh", } const entries = Object.entries(totals) -return entries.map(([state, count]) => { - const stateCode = stateCodes[state.toLowerCase()] +return entries.map(([state, count]) => + {const stateCode = stateCodes[state.toLowerCase()] return { state, count, flag: "http://flags.ox3.in/svg/us/${stateCode}.svg" } }) diff --git a/packages/builder/package.json b/packages/builder/package.json index c90fdd462c..1002bc2636 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -95,7 +95,7 @@ "@testing-library/jest-dom": "^5.11.10", "@testing-library/svelte": "^3.0.0", "babel-jest": "^26.6.3", - "cypress": "9.2.0", + "cypress": "9.2.1", "cypress-terminal-report": "^1.4.1", "identity-obj-proxy": "^3.0.0", "jest": "^26.6.3", diff --git a/packages/builder/yarn.lock b/packages/builder/yarn.lock index 476554abd1..23d7fd9225 100644 --- a/packages/builder/yarn.lock +++ b/packages/builder/yarn.lock @@ -2503,15 +2503,14 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-table3@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee" - integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ== +cli-table3@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.1.tgz#36ce9b7af4847f288d3cdd081fbd09bf7bd237b8" + integrity sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA== dependencies: - object-assign "^4.1.0" string-width "^4.2.0" optionalDependencies: - colors "^1.1.2" + colors "1.4.0" cli-truncate@^2.1.0: version "2.1.0" @@ -2592,7 +2591,7 @@ colorette@^2.0.16: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== -colors@^1.1.2: +colors@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== @@ -2737,10 +2736,10 @@ cypress-terminal-report@^1.4.1: chalk "^3.0.0" methods "^1.1.2" -cypress@9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.2.0.tgz#727c20b4662167890db81d5f6ba615231835b17d" - integrity sha512-Jn26Tprhfzh/a66Sdj9SoaYlnNX6Mjfmj5PHu2a7l3YHXhrgmavM368wjCmgrxC6KHTOv9SpMQGhAJn+upDViA== +cypress@9.2.1: + version "9.2.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.2.1.tgz#47f2457e5ca7ede48be9a4176f20f30ccf3b3902" + integrity sha512-LVEe4yWCo4xO0Vd8iYjFHRyd5ulRvM56XqMgAdn05Qb9kJ6iJdO/MmjKD8gNd768698cp1FDuSmFQZHVZGk+Og== dependencies: "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" @@ -2754,7 +2753,7 @@ cypress@9.2.0: chalk "^4.1.0" check-more-types "^2.24.0" cli-cursor "^3.1.0" - cli-table3 "~0.6.0" + cli-table3 "~0.6.1" commander "^5.1.0" common-tags "^1.8.0" dayjs "^1.10.4" @@ -5117,11 +5116,6 @@ nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== -object-assign@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"