diff --git a/.eslintrc.json b/.eslintrc.json index d69801c82..00ca42459 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,12 @@ { "root": true, - "extends": [ "@tpluscode" ], + "extends": [ + "@tpluscode", + "plugin:mocha/recommended" + ], + "plugins": [ + "mocha" + ], "parserOptions": { "project": "./tsconfig.json" }, @@ -8,7 +14,10 @@ "mocha": true }, "rules": { - "@typescript-eslint/indent": ["error", 2, { "SwitchCase": 1, "ignoredNodes": ["PropertyDefinition"] }] + "@typescript-eslint/indent": ["error", 2, { "SwitchCase": 1, "ignoredNodes": ["PropertyDefinition"] }], + "mocha/no-setup-in-describe": "warn", + "mocha/no-sibling-hooks": "warn", + "mocha/max-top-level-suites": "off" }, "overrides": [{ "files": ["*.test.ts"], diff --git a/apis/core/test/domain/csv-source/replace.test.ts b/apis/core/test/domain/csv-source/replace.test.ts index 7564c7395..b81887e02 100644 --- a/apis/core/test/domain/csv-source/replace.test.ts +++ b/apis/core/test/domain/csv-source/replace.test.ts @@ -41,6 +41,7 @@ describe('domain/csv-sources/replace', () => { describe('when source is successfully parsed', () => { let csvSourceUpdate: GraphPointer + beforeEach(async () => { // given csvSource = clownface({ dataset: $rdf.dataset() }) @@ -176,6 +177,7 @@ describe('domain/csv-sources/replace', () => { describe('when source has different column order', () => { let csvSourceUpdate: GraphPointer + beforeEach(async () => { // given csvSource = clownface({ dataset: $rdf.dataset() }) diff --git a/apis/core/test/domain/csv/parse.test.ts b/apis/core/test/domain/csv/parse.test.ts index 63cb11723..77e803568 100644 --- a/apis/core/test/domain/csv/parse.test.ts +++ b/apis/core/test/domain/csv/parse.test.ts @@ -1,8 +1,8 @@ import { expect } from 'chai' import { parse } from '../../../lib/domain/csv' -describe('domain/csv/parse', () => { - it('trims headers', async () => { +describe('domain/csv/parse', function () { + it('trims headers', async function () { // given const input = '" station_id ","\tpollutant_id\t","aggregation_id\t","\tlimitvalue","year"' @@ -14,7 +14,7 @@ describe('domain/csv/parse', () => { expect(header).to.contain.ordered.members(['station_id', 'pollutant_id', 'aggregation_id', 'limitvalue', 'year']) }) - it('parses header', async () => { + it('parses header', async function () { // given const input = '"station_id","pollutant_id","aggregation_id","limitvalue","year"' diff --git a/apis/shared-dimensions/test/lib/domain/shared-dimension/queries.test.ts b/apis/shared-dimensions/test/lib/domain/shared-dimension/queries.test.ts index 0f9d10a67..deb393ddc 100644 --- a/apis/shared-dimensions/test/lib/domain/shared-dimension/queries.test.ts +++ b/apis/shared-dimensions/test/lib/domain/shared-dimension/queries.test.ts @@ -10,11 +10,11 @@ const { parsingClient } = mdClients const ns = namespace('https://ld.admin.ch/cube/dimension/') -describe('@cube-creator/shared-dimensions-api/lib/shared-dimension/queries @SPARQL', () => { - describe('deleteDynamicTerms', () => { +describe('@cube-creator/shared-dimensions-api/lib/shared-dimension/queries @SPARQL', function () { + describe('deleteDynamicTerms', function () { beforeEach(insertTestDimensions) - it('deletes dynamic property from terms', async () => { + it('deletes dynamic property from terms', async function () { // when await deleteDynamicTerms({ dimension: ns.technologies, @@ -28,7 +28,7 @@ describe('@cube-creator/shared-dimensions-api/lib/shared-dimension/queries @SPAR ).to.eventually.be.false }) - it('does nothing when there are no deleted dimensions', async () => { + it('does nothing when there are no deleted dimensions', async function () { // when await deleteDynamicTerms({ dimension: ns.technologies, diff --git a/apis/shared-dimensions/test/lib/loader.test.ts b/apis/shared-dimensions/test/lib/loader.test.ts index d14d0de94..864909fbe 100644 --- a/apis/shared-dimensions/test/lib/loader.test.ts +++ b/apis/shared-dimensions/test/lib/loader.test.ts @@ -33,6 +33,7 @@ graph ${ex('different-graph')} { describe('shared-dimensions/lib/loader @SPARQL', () => { const req = {} as any let loader: ResourceLoader + before(async () => { loader = new Loader({ graph, diff --git a/apis/shared-dimensions/test/store/index.test.ts b/apis/shared-dimensions/test/store/index.test.ts index 28e4dadfa..2359263e3 100644 --- a/apis/shared-dimensions/test/store/index.test.ts +++ b/apis/shared-dimensions/test/store/index.test.ts @@ -18,13 +18,13 @@ const graph = $rdf.namedNode('https://lindas.admin.ch/cube/dimension') const ns = namespace('https://ld.admin.ch/cube/dimension/') const { parsingClient } = mdClients -describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', () => { +describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', function () { let store: SharedDimensionsStore - describe('exists', () => { + describe('exists', function () { before(prepareStore) - it('returns true when a resource with given type exists in graph', async () => { + it('returns true when a resource with given type exists in graph', async function () { // given const id = $rdf.namedNode('https://ld.admin.ch/cube/dimension/technologies') @@ -34,7 +34,7 @@ describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', () => { await expect(exists).to.eventually.be.true }) - it('returns false when a resource with given type exists in a different graph', async () => { + it('returns false when a resource with given type exists in a different graph', async function () { // given const id = $rdf.namedNode('https://ld.admin.ch/cube/dimension/technologies') await INSERT.DATA` @@ -49,7 +49,7 @@ describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', () => { await expect(exists).to.eventually.be.true }) - it('returns false when a resource exists in graph but not with the type', async () => { + it('returns false when a resource exists in graph but not with the type', async function () { // given const id = $rdf.namedNode('https://ld.admin.ch/cube/dimension/technologies') @@ -59,7 +59,7 @@ describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', () => { await expect(exists).to.eventually.be.false }) - it('returns false when a resource does no exist', async () => { + it('returns false when a resource does no exist', async function () { // given const id = $rdf.namedNode('https://ld.admin.ch/cube/dimension/foobar') @@ -70,9 +70,10 @@ describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', () => { }) }) - describe('load', () => { + describe('load', function () { beforeEach(prepareStore) - before(async () => { + + before(async function () { await INSERT.DATA` GRAPH ${ex.otherGraph} { ${ns['technologies/sparql']} a ${ex.Something} @@ -80,14 +81,14 @@ describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', () => { `.execute(parsingClient.query) }) - context('existing dimension', () => { + context('existing dimension', function () { let dimension: GraphPointer - before(async () => { + before(async function () { dimension = await store.load(ns.technologies) }) - it('loads base properties', () => { + it('loads base properties', function () { expect(dimension).to.matchShape({ property: [{ path: rdf.type, @@ -106,7 +107,7 @@ describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', () => { }) }) - it('loads dynamic properties', () => { + it('loads dynamic properties', function () { const langStringProperty: Initializer = { property: [{ path: md.dynamicPropertyType, @@ -177,14 +178,14 @@ describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', () => { }) }) - context('term with dynamic properties', () => { + context('term with dynamic properties', function () { let dimensionTerm: GraphPointer - beforeEach(async () => { + beforeEach(async function () { dimensionTerm = await store.load(ns['technologies/sparql']) }) - it('does not load data from other graphs', async () => { + it('does not load data from other graphs', async function () { expect(dimensionTerm).not.to.matchShape({ property: { path: rdf.type, @@ -193,7 +194,7 @@ describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', () => { }) }) - it('gets loaded with common properties', () => { + it('gets loaded with common properties', function () { expect(dimensionTerm).to.matchShape({ property: [{ path: schema.validFrom, @@ -214,7 +215,7 @@ describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', () => { }) }) - it('gets loaded with dynamic property values', () => { + it('gets loaded with dynamic property values', function () { expect(dimensionTerm).to.matchShape({ property: [{ path: rdfs.comment, @@ -231,9 +232,10 @@ describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', () => { }) }) - describe('delete', () => { + describe('delete', function () { beforeEach(prepareStore) - beforeEach(async () => { + + beforeEach(async function () { await INSERT.DATA` GRAPH ${ex.otherGraph} { ${ns['technologies/sparql']} a ${ex.Something} @@ -241,7 +243,7 @@ describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', () => { `.execute(parsingClient.query) }) - it('does not delete data from other graphs', async () => { + it('does not delete data from other graphs', async function () { // given const term = ns['technologies/sparql'] @@ -252,7 +254,7 @@ describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', () => { await expect(ASK`${term} ?p ?o`.FROM(ex.otherGraph).execute(parsingClient.query)).to.eventually.be.true }) - it('deletes dimension term with dynamic properties', async () => { + it('deletes dimension term with dynamic properties', async function () { // given const term = ns['technologies/sparql'] @@ -263,7 +265,7 @@ describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', () => { await expect(ASK`${term} ?p ?o`.FROM(graph).execute(parsingClient.query)).to.eventually.be.false }) - it('deletes dimension deep', async () => { + it('deletes dimension deep', async function () { // given const term = ns.technologies @@ -275,10 +277,10 @@ describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', () => { }) }) - describe('save', () => { + describe('save', function () { before(prepareStore) - it('inserts new resource', async () => { + it('inserts new resource', async function () { // given const term = namedNode(ns['technologies/owl']) .addOut(rdf.type, [md.SharedDimensionTerm, hydra.Resource, schema.DefinedTerm]) @@ -300,7 +302,7 @@ describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', () => { `.execute(parsingClient.query)).to.eventually.be.true }) - it('updates existing resource', async () => { + it('updates existing resource', async function () { // given const term = namedNode(ns['technologies/rdf']) .addOut(rdf.type, [md.SharedDimensionTerm, hydra.Resource, schema.DefinedTerm]) @@ -325,7 +327,7 @@ describe('@cube-creator/shared-dimensions-api/lib/store/index @SPARQL', () => { `.execute(parsingClient.query)).to.eventually.be.false }) - it('updates dynamic properties', async () => { + it('updates dynamic properties', async function () { // given const term = namedNode(ns['technologies/sparql']) .addOut(rdf.type, [md.SharedDimensionTerm, hydra.Resource, schema.DefinedTerm]) diff --git a/cli/test/lib/commands/publish.test.ts b/cli/test/lib/commands/publish.test.ts index bf38ec92f..b0afcda50 100644 --- a/cli/test/lib/commands/publish.test.ts +++ b/cli/test/lib/commands/publish.test.ts @@ -151,6 +151,7 @@ describe('@cube-creator/cli/lib/commands/publish', function () { describe('publishing published', () => { before(resetData) + before(function makeCubePublished() { return WITH(job, DELETE` ${job} ${schema.creativeWorkStatus} ?status @@ -160,10 +161,13 @@ describe('@cube-creator/cli/lib/commands/publish', function () { ${job} ${schema.creativeWorkStatus} ?status `).execute(ccClients.parsingClient.query) }) + before(runJob) it('removes hydra terms', removesHydraTerms) + it('removes original values of mapped dimensions', removesMappingsOriginalValues) + it('adds qudt:hasUnit', duplicatesQudtUnit) it('does not publish a cube with trailing slash', async () => { @@ -214,11 +218,15 @@ describe('@cube-creator/cli/lib/commands/publish', function () { describe('publishing draft', () => { before(resetData) + before(runJob) it('removes hydra terms', removesHydraTerms) + it('adds qudt:hasUnit', duplicatesQudtUnit) + it('removes original values of mapped dimensions', removesMappingsOriginalValues) + it('adds software versions', addsSoftwareVersions) it('does not remove previously published triples', () => { diff --git a/cli/test/lib/commands/unlist.test.ts b/cli/test/lib/commands/unlist.test.ts index ff275bf50..9a48b5d23 100644 --- a/cli/test/lib/commands/unlist.test.ts +++ b/cli/test/lib/commands/unlist.test.ts @@ -63,6 +63,7 @@ describe('@cube-creator/cli/lib/commands/unlist', function () { } before(resetData) + before(runJob) it('"deprecates" previous cubes', async function () { diff --git a/package.json b/package.json index b31ad6f6a..1ca6727ca 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "eslint-config-standard": "^17.0.0", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.26.0", + "eslint-plugin-mocha": "^10.5.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-vue": "^9.8.0", diff --git a/packages/model/test/lib/datatypeInference.test.ts b/packages/model/test/lib/datatypeInference.test.ts index 9423f821c..c4212b1b1 100644 --- a/packages/model/test/lib/datatypeInference.test.ts +++ b/packages/model/test/lib/datatypeInference.test.ts @@ -7,49 +7,64 @@ describe('@cube-creator/model/DatatypeChecker', () => { it('recognize xsd:integer', () => { expect(inferDatatype(['42'])).to.eq(xsd.integer) }) + it('recognize xsd:decimal', () => { expect(inferDatatype(['42.1'])).to.eq(xsd.decimal) }) + it('recognize xsd:boolean', () => { // if the first value was 0 or 1, it would be considered as xsd:integer expect(inferDatatype(['true', 'false', '0', '1'])).to.eq(xsd.boolean) }) + it('recognize xsd:date', () => { expect(inferDatatype(['2021-01-01'])).to.eq(xsd.date) }) + it('recognize xsd:time', () => { expect(inferDatatype(['23:57:05'])).to.eq(xsd.time) }) + it('recognize xsd:dateTime', () => { expect(inferDatatype(['2021-01-01T23:57:05'])).to.eq(xsd.dateTime) }) + it('recognize xsd:gYearMonth', () => { expect(inferDatatype(['2021-12'])).to.eq(xsd.gYearMonth) }) + it('recognize xsd:string', () => { expect(inferDatatype(['abc'])).to.eq(xsd.string) }) + it('recognize two xsd:integer values', () => { expect(inferDatatype(['42', '42'])).to.eq(xsd.integer) }) + it('recognize xsd:string with empty array', () => { expect(inferDatatype([])).to.eq(xsd.string) }) + it('recognize xsd:string with empty string', () => { expect(inferDatatype([''])).to.eq(xsd.string) }) + it('recognize xd:integer ignoring empty strings', () => { expect(inferDatatype(['', '42', ''])).to.eq(xsd.integer) }) + it('recognize xsd:string after xsd:date', () => { expect(inferDatatype(['2021-01-01', 'foo'])).to.eq(xsd.string) }) + it('recognize xsd:decimal after xsd:integer', () => { expect(inferDatatype(['42', '42.1'])).to.eq(xsd.decimal) }) + it('recognize xsd:string after xsd:integer', () => { expect(inferDatatype(['42', 'foo'])).to.eq(xsd.string) }) + it('recognize xd:string when mixed types', () => { expect(inferDatatype(['', '42', '2021-01-01'])).to.eq(xsd.string) }) diff --git a/yarn.lock b/yarn.lock index 29a90a00c..22fcef3fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6110,7 +6110,7 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001297, caniuse-lite@^1.0.30001663, caniuse-lite@^1.0.30001669: +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001297, caniuse-lite@^1.0.30001669: version "1.0.30001675" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001675.tgz#0c1f01fc9cc543b61839753a4c234f995588d1b9" integrity sha512-/wV1bQwPrkLiQMjaJF5yUMVM/VdRPOCU8QZ+PmG6uW6DvYSrNY1bpwHI/3mOcUosLaJCzYDi5o91IQB51ft6cg== @@ -7532,7 +7532,7 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.5.28, electron-to-chromium@^1.5.41: +electron-to-chromium@^1.5.41: version "1.5.49" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.49.tgz#9358f514ab6eeed809a8689f4b39ea5114ae729c" integrity sha512-ZXfs1Of8fDb6z7WEYZjXpgIRF6MEu8JdeGA0A40aZq6OQbS+eJpnnV49epZRna2DU/YsEjSQuGtQPPtvt6J65A== @@ -7721,7 +7721,7 @@ es6-url-template@^3.0.2: resolved "https://registry.yarnpkg.com/es6-url-template/-/es6-url-template-3.0.2.tgz#99632e854cd1c5a88fffaf3e4dca8e2f72d89995" integrity sha512-8ZqWPZ8aa9FoyZlGlC+s/d84VW7glQA3WwvDTl0Ykmwi/fX1aEmd6RWoBprX7QWzwR5zrv8RIokfj6kLmcdYzA== -escalade@^3.1.1, escalade@^3.1.2, escalade@^3.2.0: +escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== @@ -7839,6 +7839,15 @@ eslint-plugin-import@^2.26.0: resolve "^1.22.0" tsconfig-paths "^3.14.1" +eslint-plugin-mocha@^10.5.0: + version "10.5.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-mocha/-/eslint-plugin-mocha-10.5.0.tgz#0aca8d709e7cddef566e0dc252f6b02e307a2b7e" + integrity sha512-F2ALmQVPT1GoP27O1JTZGrV9Pqg8k79OeIuvw63UxMtQKREZtmkK1NFgkZQ2TW7L2JSSFKHFPTtHu5z8R9QNRw== + dependencies: + eslint-utils "^3.0.0" + globals "^13.24.0" + rambda "^7.4.0" + eslint-plugin-n@^15.1.0, eslint-plugin-n@^15.2.4: version "15.6.1" resolved "https://registry.yarnpkg.com/eslint-plugin-n/-/eslint-plugin-n-15.6.1.tgz#f7e77f24abb92a550115cf11e29695da122c398c" @@ -9004,10 +9013,10 @@ globals@^11.1.0, globals@^11.12.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.19.0: - version "13.20.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" - integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== +globals@^13.19.0, globals@^13.24.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== dependencies: type-fest "^0.20.2" @@ -12244,7 +12253,7 @@ picocolors@^0.2.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== -picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0: +picocolors@^1.0.0, picocolors@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -12864,6 +12873,11 @@ quick-lru@^5.1.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== +rambda@^7.4.0: + version "7.5.0" + resolved "https://registry.yarnpkg.com/rambda/-/rambda-7.5.0.tgz#1865044c59bc0b16f63026c6e5a97e4b1bbe98fe" + integrity sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA== + "ramda@npm:@pnpm/ramda@0.28.1": version "0.28.1" resolved "https://registry.yarnpkg.com/@pnpm/ramda/-/ramda-0.28.1.tgz#0f32abc5275d586a03e0dc1dd90a009ac668ff33" @@ -14991,7 +15005,7 @@ upath@^1.2.0: resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== -update-browserslist-db@^1.1.0, update-browserslist-db@^1.1.1: +update-browserslist-db@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==