Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

major: ACNA-2584 - add Adobe App Builder State Store #134

Merged
merged 16 commits into from
Jan 8, 2024
Merged
4 changes: 4 additions & 0 deletions e2e/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
TEST_NAMESPACE_1=
TEST_AUTH_1=
TEST_NAMESPACE_2=
TEST_AUTH_2=
93 changes: 49 additions & 44 deletions e2e/e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,22 @@ governing permissions and limitations under the License.
/* ************* NOTE 2: requires env vars TEST_AUTH_1, TEST_NS_1 and TEST_AUTH_2, TEST_NS_2 for 2 different namespaces. ************* */

const stateLib = require('../index')
const { codes } = require('../lib/StateStoreError')
const path = require('node:path')
const { MAX_TTL_SECONDS } = require('../lib/constants')

// load .env values in the e2e folder, if any
require('dotenv').config({ path: path.join(__dirname, '.env') })

const testKey = 'e2e_test_state_key'

jest.setTimeout(30000) // thirty seconds per test

beforeEach(() => {
expect.hasAssertions()
})

const initStateEnv = async (n = 1) => {
delete process.env.__OW_API_KEY
delete process.env.__OW_NAMESPACE
process.env.__OW_API_KEY = process.env[`TEST_AUTH_${n}`]
process.env.__OW_NAMESPACE = process.env[`TEST_NAMESPACE_${n}`]
// 1. init will fetch credentials from the tvm using ow creds
const state = await stateLib.init() // { tvm: { cacheFile: false } } // keep cache for better perf?
const state = await stateLib.init()
// make sure we delete the testKey, note that delete might fail as it is an op under test
await state.delete(testKey)
return state
Expand All @@ -44,82 +43,83 @@ describe('e2e tests using OpenWhisk credentials (as env vars)', () => {
delete process.env.__OW_NAMESPACE
process.env.__OW_API_KEY = process.env.TEST_AUTH_1
process.env.__OW_NAMESPACE = process.env.TEST_NAMESPACE_1 + 'bad'
let expectedError

try {
await stateLib.init()
const store = await stateLib.init()
await store.get('something')
} catch (e) {
expect({ name: e.name, code: e.code, message: e.message, sdkDetails: e.sdkDetails }).toEqual(expect.objectContaining({
name: 'StateLibError',
expectedError = e
}

expect(expectedError).toBeDefined()
expect(expectedError instanceof Error).toBeTruthy()
expect({ name: expectedError.name, code: expectedError.code, message: expectedError.message, sdkDetails: expectedError.sdkDetails })
.toEqual(expect.objectContaining({
name: 'AdobeStateLibError',
code: 'ERROR_BAD_CREDENTIALS'
}))
}
})

test('key-value basic test on one key with string value: get, write, get, delete, get', async () => {
test('key-value basic test on one key with string value: put, get, delete, any, deleteAll', async () => {
const state = await initStateEnv()

const testValue = 'a string'

expect(await state.get(testKey)).toEqual(undefined)
expect(await state.put(testKey, testValue)).toEqual(testKey)
expect(await state.get(testKey)).toEqual(expect.objectContaining({ value: testValue }))
expect(await state.delete(testKey, testValue)).toEqual(testKey)
expect(await state.get(testKey)).toEqual(undefined)
})

test('key-value basic test on one key with object value: get, write, get, delete, get', async () => {
const state = await initStateEnv()

const testValue = { a: 'fake', object: { with: { multiple: 'layers' }, that: { dreams: { of: { being: 'real' } } } } }

expect(await state.delete(testKey)).toEqual(testKey)
expect(await state.get(testKey)).toEqual(undefined)
expect(await state.any()).toEqual(false)
expect(await state.put(testKey, testValue)).toEqual(testKey)
expect(await state.get(testKey)).toEqual(expect.objectContaining({ value: testValue }))
expect(await state.delete(testKey, testValue)).toEqual(testKey)
expect(await state.any()).toEqual(true)
expect(await state.deleteAll()).toEqual(true)
expect(await state.get(testKey)).toEqual(undefined)
expect(await state.any()).toEqual(false)
})

test('time-to-live tests: write w/o ttl, get default ttl, write with ttl, get, get after ttl', async () => {
const state = await initStateEnv()

const testValue = { an: 'object' }
const testValue = 'test value'
let res, resTime

// 1. test default ttl = 1 day
expect(await state.put(testKey, testValue)).toEqual(testKey)
let res = await state.get(testKey)
expect(new Date(res.expiration).getTime()).toBeLessThanOrEqual(new Date(Date.now() + 86400000).getTime()) // 86400000 ms = 1 day
expect(new Date(res.expiration).getTime()).toBeGreaterThanOrEqual(new Date(Date.now() + 86400000 - 10000).getTime()) // give more or less 10 seconds clock skew + request time
res = await state.get(testKey)
resTime = new Date(res.expiration).getTime()
expect(resTime).toBeLessThanOrEqual(new Date(Date.now() + 86400000).getTime()) // 86400000 ms = 1 day
expect(resTime).toBeGreaterThanOrEqual(new Date(Date.now() + 86400000 - 10000).getTime()) // give more or less 10 seconds clock skew + request time

// 2. test infinite ttl
// 2. test max ttl
const nowPlus365Days = new Date(MAX_TTL_SECONDS).getTime()
expect(await state.put(testKey, testValue, { ttl: -1 })).toEqual(testKey)
expect(await state.get(testKey)).toEqual(expect.objectContaining({ expiration: null }))
res = await state.get(testKey)
resTime = new Date(res.expiration).getTime()
expect(resTime).toBeGreaterThanOrEqual(nowPlus365Days)

// 3. test that after ttl object is deleted
expect(await state.put(testKey, testValue, { ttl: 2 })).toEqual(testKey)
res = await state.get(testKey)
expect(new Date(res.expiration).getTime()).toBeLessThanOrEqual(new Date(Date.now() + 2000).getTime())
await waitFor(3000) // give it one more sec - azure ttl is not so precise
await waitFor(3000) // give it one more sec - ttl is not so precise
expect(await state.get(testKey)).toEqual(undefined)
})

test('throw error when get/put with invalid keys', async () => {
const invalidChars = "The following characters are restricted and cannot be used in the Id property: '/', '\\', '?', '#' "
const invalidKey = 'invalid/key'
const invalidKey = 'some/invalid/key'
const state = await initStateEnv()
await expect(state.put(invalidKey, 'testValue')).rejects.toThrow(new codes.ERROR_BAD_REQUEST({
messageValues: [invalidChars]
}))
await expect(state.get(invalidKey)).rejects.toThrow(new codes.ERROR_BAD_REQUEST({
messageValues: [invalidChars]
}))
await expect(state.put(invalidKey, 'testValue')).rejects.toThrow('[AdobeStateLib:ERROR_BAD_ARGUMENT] invalid key and/or value')
await expect(state.get(invalidKey)).rejects.toThrow('[AdobeStateLib:ERROR_BAD_ARGUMENT] invalid key')
})

test('isolation tests: get, write, delete on same key for two namespaces do not interfere', async () => {
const state1 = await initStateEnv(1)
const state2 = await initStateEnv(2)

const testValue1 = { an: 'object' }
const testValue2 = { another: 'dummy' }
const testValue1 = 'one value'
const testValue2 = 'some other value'

// 1. test that ns2 cannot get state in ns1
await state1.put(testKey, testValue1)
Expand All @@ -139,16 +139,21 @@ describe('e2e tests using OpenWhisk credentials (as env vars)', () => {

test('error value bigger than 2MB test', async () => {
const state = await initStateEnv()

const bigValue = ('a').repeat(1024 * 1024 * 2 + 1)
let expectedError

try {
await state.put(testKey, bigValue)
} catch (e) {
expect({ name: e.name, code: e.code, message: e.message, sdkDetails: e.sdkDetails }).toEqual(expect.objectContaining({
name: 'StateLibError',
expectedError = e
}

expect(expectedError).toBeDefined()
expect(expectedError instanceof Error).toBeTruthy()
expect({ name: expectedError.name, code: expectedError.code, message: expectedError.message, sdkDetails: expectedError.sdkDetails })
.toEqual(expect.objectContaining({
name: 'AdobeStateLibError',
code: 'ERROR_PAYLOAD_TOO_LARGE'
}))
}
})
})
4 changes: 3 additions & 1 deletion e2e/e2e.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
## Requirements

- To run the test you'll need two OpenWhisk namespaces. Please set the credentials for those in the following env
variables:
variables in an .env file:
- `TEST_NAMESPACE_1, TEST_AUTH_1, TEST_NAMESPACE_2, TEST_AUTH_2`

Copy the `.env.example` to your own `.env` in this folder.

## Run

`npm run e2e`
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTA
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
require('./lib/StateStore')
require('./lib/AdobeStateStore')
module.exports = require('./lib/init')
10 changes: 9 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,13 @@ module.exports = {
collectCoverageFrom: [
'index.js',
'lib/**/*.js'
]
],
coverageThreshold: {
global: {
branches: 100,
lines: 100,
statements: 100,
functions: 100
}
}
}
Loading