Compare commits

...

10 Commits

Author SHA1 Message Date
Thomas Carmet 9491909355 ARSN-20 migrate to github actions
Co-authored-by: Ronnie <halfpint1170@gmail.com>
(cherry picked from commit 4b08dd5263)
2022-01-10 13:20:26 -08:00
Jonathan Gramain 270a478ac6 ARSN-42 bump version to 7.4.13
(cherry picked from commit 5ce057a498)
2022-01-10 13:06:07 -08:00
Jonathan Gramain df37a5f396 improvement: ARSN-42 get/set ObjectMD.nullUploadId
Add getNullUploadId/setNullUploadId helpers to ObjectMD, to store the
null version uploadId, so that it can be passed to the metadata layer
as "replayId" when deleting the null version from another master key

(cherry picked from commit 8c3f88e233)
2022-01-10 13:02:55 -08:00
Jonathan Gramain c1f692c4fe feature: ARSN-38 introduce replay prefix hidden in listings
- Add a new DB prefix for replay keys, similar to existing v1 vformat
  prefixes

- Hide this prefix for v0 listing algos DelimiterMaster and
  DelimiterVersions: skip keys beginning with this prefix, and update
  the "skipping" value to be able to skip the entire prefix after the
  streak length is reached (similar to how regular prefixes are
  skipped)

- fix an existing unit test in DelimiterVersions

(cherry picked from commit abfbe90a57)
2022-01-10 13:02:55 -08:00
Jonathan Gramain 3df81a4288 feature: ARSN-37 ObjectMD getUploadId/setUploadId
Add getter/setter for the "uploadId" field, used for MPUs in progress.

(cherry picked from commit b1c9474159)
2022-01-10 13:02:55 -08:00
Ronnie Smith 0277677320
bugfix: S3C-3810 Skip headers on 304 response
(cherry picked from commit 836c65e91e)
2021-07-30 17:13:42 -07:00
Jonathan Gramain 57c20119d2 bugfix: S3C-4275 enable skip-scan for DelimiterVersions with a delimiter
Enable the skip-scan optimization to work for DelimiterVersions
listing algorithm when used with a delimiter.

For this to work, instead of returning FILTER_ACCEPT when encountering
a version that matches the master key (which resets the skip-scan
counter), return FILTER_SKIP to let the skip-scan counter increment
and eventually skip the entire listed common prefix after 100 entries.

(cherry picked from commit ecaf9f843a)
2021-04-13 18:26:50 -07:00
Jonathan Gramain 4fd7bce561 bugfix: S3C-4275 more DelimiterVersions unit tests
Increase coverage for DelimiterVersions listing algorithm to have it
in par with DelimiterMaster before attempting a fix: most existing
tests from DelimiterMaster have been copied and adapted to fit the
DelimiterVersions logic.

(cherry picked from commit 3506fd9f4e)
2021-04-13 18:26:50 -07:00
Taylor McKinnon c4d17d51d2 add BypassGovernanceRetention to action map
(cherry picked from commit 71c1c01b35)
2021-04-13 17:39:15 -07:00
naren-scality 11458e8886 bf S3C-4239 log consumer callback error fix
A guard is added to ensure that the callback is called only once in the
event of an error while reading records in log consumer.

(cherry picked from commit 941b644e9e)
2021-04-12 12:20:29 -07:00
16 changed files with 709 additions and 161 deletions

47
.github/workflows/tests.yaml vendored Normal file
View File

@ -0,0 +1,47 @@
---
name: tests
on:
push:
branches-ignore:
- 'development/**'
jobs:
test:
runs-on: ubuntu-latest
services:
# Label used to access the service container
redis:
# Docker Hub image
image: redis
# Set health checks to wait until redis has started
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps port 6379 on service container to the host
- 6379:6379
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '10'
cache: 'yarn'
- name: install dependencies
run: yarn install --frozen-lockfile
- name: lint yaml
run: yarn --silent lint_yml
- name: lint javascript
run: yarn --silent lint -- --max-warnings 0
- name: lint markdown
run: yarn --silent lint_md
- name: run unit tests
run: yarn --silent test
- name: run functional tests
run: yarn ft_test
- name: run executables tests
run: yarn install && yarn test
working-directory: 'lib/executables/pensieveCreds/'

View File

@ -1,43 +0,0 @@
---
version: 0.2
branches:
default:
stage: pre-merge
stages:
pre-merge:
worker: &master-worker
type: docker
path: eve/workers/master
volumes:
- '/home/eve/workspace'
steps:
- Git:
name: fetch source
repourl: '%(prop:git_reference)s'
shallow: True
retryFetch: True
haltOnFailure: True
- ShellCommand:
name: install dependencies
command: yarn install --frozen-lockfile
- ShellCommand:
name: run lint yml
command: yarn run --silent lint_yml
- ShellCommand:
name: run lint
command: yarn run --silent lint -- --max-warnings 0
- ShellCommand:
name: run lint_md
command: yarn run --silent lint_md
- ShellCommand:
name: run test
command: yarn run --silent test
- ShellCommand:
name: run ft_test
command: yarn run ft_test
- ShellCommand:
name: run executables tests
command: yarn install && yarn test
workdir: '%(prop:builddir)s/build/lib/executables/pensieveCreds/'

View File

@ -1,57 +0,0 @@
FROM ubuntu:trusty
#
# Install apt packages needed by the buildchain
#
ENV LANG C.UTF-8
COPY buildbot_worker_packages.list arsenal_packages.list /tmp/
RUN apt-get update -q && apt-get -qy install curl apt-transport-https \
&& apt-get install -qy software-properties-common python-software-properties \
&& curl --silent https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \
&& echo "deb https://deb.nodesource.com/node_10.x trusty main" > /etc/apt/sources.list.d/nodesource.list \
&& curl -sS http://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& echo "deb http://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
&& add-apt-repository ppa:ubuntu-toolchain-r/test \
&& apt-get update -q \
&& cat /tmp/buildbot_worker_packages.list | xargs apt-get install -qy \
&& cat /tmp/arsenal_packages.list | xargs apt-get install -qy \
&& pip install pip==9.0.1 \
&& rm -rf /var/lib/apt/lists/* \
&& rm -f /tmp/*_packages.list
#
# Install usefull nodejs dependencies
#
RUN yarn global add mocha
#
# Add user eve
#
RUN adduser -u 1042 --home /home/eve --disabled-password --gecos "" eve \
&& adduser eve sudo \
&& sed -ri 's/(%sudo.*)ALL$/\1NOPASSWD:ALL/' /etc/sudoers
#
# Run buildbot-worker on startup
#
ARG BUILDBOT_VERSION=0.9.12
RUN pip install yamllint
RUN pip install buildbot-worker==$BUILDBOT_VERSION
USER eve
ENV HOME /home/eve
#
# Setup nodejs environmnent
#
ENV CXX=g++-4.9
ENV LANG C.UTF-8
WORKDIR /home/eve/workspace
CMD buildbot-worker create-worker . "$BUILDMASTER:$BUILDMASTER_PORT" "$WORKERNAME" "$WORKERPASS" \
&& sudo service redis-server start \
&& buildbot-worker start --nodaemon

View File

@ -1,4 +0,0 @@
nodejs
redis-server
g++-4.9
yarn

View File

@ -1,9 +0,0 @@
ca-certificates
git
libffi-dev
libssl-dev
python2.7
python2.7-dev
python-pip
software-properties-common
sudo

View File

@ -32,6 +32,7 @@ class DelimiterMaster extends Delimiter {
// non-PHD master version or a version whose master is a PHD version // non-PHD master version or a version whose master is a PHD version
this.prvKey = undefined; this.prvKey = undefined;
this.prvPHDKey = undefined; this.prvPHDKey = undefined;
this.inReplayPrefix = false;
Object.assign(this, { Object.assign(this, {
[BucketVersioningKeyFormat.v0]: { [BucketVersioningKeyFormat.v0]: {
@ -61,6 +62,12 @@ class DelimiterMaster extends Delimiter {
let key = obj.key; let key = obj.key;
const value = obj.value; const value = obj.value;
if (key.startsWith(DbPrefixes.Replay)) {
this.inReplayPrefix = true;
return FILTER_SKIP;
}
this.inReplayPrefix = false;
/* Skip keys not starting with the prefix or not alphabetically /* Skip keys not starting with the prefix or not alphabetically
* ordered. */ * ordered. */
if ((this.prefix && !key.startsWith(this.prefix)) if ((this.prefix && !key.startsWith(this.prefix))
@ -80,13 +87,13 @@ class DelimiterMaster extends Delimiter {
* when a listing page ends on an accepted obj and the next page * when a listing page ends on an accepted obj and the next page
* starts with a version of this object. * starts with a version of this object.
* In that case prvKey is default set to undefined * In that case prvKey is default set to undefined
* in the constructor) and comparing to NextMarker is the only * in the constructor and comparing to NextMarker is the only
* way to know we should not accept this version. This test is * way to know we should not accept this version. This test is
* not redundant with the one at the beginning of this function, * not redundant with the one at the beginning of this function,
* we are comparing here the key without the version suffix, * we are comparing here the key without the version suffix,
* - key startsWith the previous NextMarker happens because we set * - key startsWith the previous NextMarker happens because we set
* NextMarker to the common prefix instead of the whole key * NextMarker to the common prefix instead of the whole key
* value. (TODO: remove this test once ZENKO-1048 is fixed. ). * value. (TODO: remove this test once ZENKO-1048 is fixed)
* */ * */
if (key === this.prvKey || key === this[this.nextContinueMarker] || if (key === this.prvKey || key === this[this.nextContinueMarker] ||
(this.delimiter && (this.delimiter &&
@ -155,7 +162,7 @@ class DelimiterMaster extends Delimiter {
return super.filter(obj); return super.filter(obj);
} }
skippingV0() { skippingBase() {
if (this[this.nextContinueMarker]) { if (this[this.nextContinueMarker]) {
// next marker or next continuation token: // next marker or next continuation token:
// - foo/ : skipping foo/ // - foo/ : skipping foo/
@ -170,8 +177,15 @@ class DelimiterMaster extends Delimiter {
return SKIP_NONE; return SKIP_NONE;
} }
skippingV0() {
if (this.inReplayPrefix) {
return DbPrefixes.Replay;
}
return this.skippingBase();
}
skippingV1() { skippingV1() {
const skipTo = this.skippingV0(); const skipTo = this.skippingBase();
if (skipTo === SKIP_NONE) { if (skipTo === SKIP_NONE) {
return SKIP_NONE; return SKIP_NONE;
} }

View File

@ -33,6 +33,7 @@ class DelimiterVersions extends Delimiter {
// listing results // listing results
this.NextMarker = parameters.keyMarker; this.NextMarker = parameters.keyMarker;
this.NextVersionIdMarker = undefined; this.NextVersionIdMarker = undefined;
this.inReplayPrefix = false;
Object.assign(this, { Object.assign(this, {
[BucketVersioningKeyFormat.v0]: { [BucketVersioningKeyFormat.v0]: {
@ -163,8 +164,15 @@ class DelimiterVersions extends Delimiter {
* @return {number} - indicates if iteration should continue * @return {number} - indicates if iteration should continue
*/ */
filterV0(obj) { filterV0(obj) {
if (obj.key.startsWith(DbPrefixes.Replay)) {
this.inReplayPrefix = true;
return FILTER_SKIP;
}
this.inReplayPrefix = false;
if (Version.isPHD(obj.value)) { if (Version.isPHD(obj.value)) {
return FILTER_ACCEPT; // trick repd to not increase its streak // return accept to avoid skipping the next values in range
return FILTER_ACCEPT;
} }
return this.filterCommon(obj.key, obj.value); return this.filterCommon(obj.key, obj.value);
} }
@ -205,8 +213,9 @@ class DelimiterVersions extends Delimiter {
} else { } else {
nonversionedKey = key.slice(0, versionIdIndex); nonversionedKey = key.slice(0, versionIdIndex);
versionId = key.slice(versionIdIndex + 1); versionId = key.slice(versionIdIndex + 1);
// skip a version key if it is the master version
if (this.masterKey === nonversionedKey && this.masterVersionId === versionId) { if (this.masterKey === nonversionedKey && this.masterVersionId === versionId) {
return FILTER_ACCEPT; // trick repd to not increase its streak return FILTER_SKIP;
} }
this.masterKey = undefined; this.masterKey = undefined;
this.masterVersionId = undefined; this.masterVersionId = undefined;
@ -222,6 +231,9 @@ class DelimiterVersions extends Delimiter {
} }
skippingV0() { skippingV0() {
if (this.inReplayPrefix) {
return DbPrefixes.Replay;
}
if (this.NextMarker) { if (this.NextMarker) {
const index = this.NextMarker.lastIndexOf(this.delimiter); const index = this.NextMarker.lastIndexOf(this.delimiter);
if (index === this.NextMarker.length - 1) { if (index === this.NextMarker.length - 1) {

View File

@ -110,8 +110,10 @@ class ObjectMD {
// should be undefined when not set explicitly // should be undefined when not set explicitly
'isNull': undefined, 'isNull': undefined,
'nullVersionId': undefined, 'nullVersionId': undefined,
'nullUploadId': undefined,
'isDeleteMarker': undefined, 'isDeleteMarker': undefined,
'versionId': undefined, 'versionId': undefined,
'uploadId': undefined,
'tags': {}, 'tags': {},
'replicationInfo': { 'replicationInfo': {
status: '', status: '',
@ -631,6 +633,27 @@ class ObjectMD {
return this._data.nullVersionId; return this._data.nullVersionId;
} }
/**
* Set metadata nullUploadId value
*
* @param {string} nullUploadId - The upload ID used to complete
* the MPU of the null version
* @return {ObjectMD} itself
*/
setNullUploadId(nullUploadId) {
this._data.nullUploadId = nullUploadId;
return this;
}
/**
* Get metadata nullUploadId value
*
* @return {string|undefined} The object nullUploadId
*/
getNullUploadId() {
return this._data.nullUploadId;
}
/** /**
* Set metadata isDeleteMarker value * Set metadata isDeleteMarker value
* *
@ -681,6 +704,26 @@ class ObjectMD {
return VersionIDUtils.encode(this.getVersionId()); return VersionIDUtils.encode(this.getVersionId());
} }
/**
* Set metadata uploadId value
*
* @param {string} uploadId - The upload ID used to complete the MPU object
* @return {ObjectMD} itself
*/
setUploadId(uploadId) {
this._data.uploadId = uploadId;
return this;
}
/**
* Get metadata uploadId value
*
* @return {string|undefined} The object uploadId
*/
getUploadId() {
return this._data.uploadId;
}
/** /**
* Set tags * Set tags
* *

View File

@ -23,6 +23,7 @@ const sharedActionMap = {
bucketPutReplication: 's3:PutReplicationConfiguration', bucketPutReplication: 's3:PutReplicationConfiguration',
bucketPutVersioning: 's3:PutBucketVersioning', bucketPutVersioning: 's3:PutBucketVersioning',
bucketPutWebsite: 's3:PutBucketWebsite', bucketPutWebsite: 's3:PutBucketWebsite',
bypassGovernanceRetention: 's3:BypassGovernanceRetention',
listMultipartUploads: 's3:ListBucketMultipartUploads', listMultipartUploads: 's3:ListBucketMultipartUploads',
listParts: 's3:ListMultipartUploadParts', listParts: 's3:ListMultipartUploadParts',
multipartDelete: 's3:AbortMultipartUpload', multipartDelete: 's3:AbortMultipartUpload',

View File

@ -85,8 +85,18 @@ const XMLResponseBackend = {
}); });
}, },
errorResponse: function errorXMLResponse(errCode, response, log, errorResponse: function errorXMLResponse(errCode, response, log, corsHeaders) {
corsHeaders) { setCommonResponseHeaders(corsHeaders, response, log);
// early return to avoid extra headers and XML data
if (errCode.code === 304) {
response.writeHead(errCode.code);
return response.end('', 'utf8', () => {
log.end().info('responded with empty body', {
httpCode: response.statusCode,
});
});
}
log.trace('sending error xml response', { errCode }); log.trace('sending error xml response', { errCode });
/* /*
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
@ -112,7 +122,6 @@ const XMLResponseBackend = {
log.addDefaultFields({ log.addDefaultFields({
bytesSent, bytesSent,
}); });
setCommonResponseHeaders(corsHeaders, response, log);
response.writeHead(errCode.code, response.writeHead(errCode.code,
{ 'Content-Type': 'application/xml', { 'Content-Type': 'application/xml',
'Content-Length': bytesSent }); 'Content-Length': bytesSent });

View File

@ -6,6 +6,7 @@ const jsonStream = require('JSONStream');
const werelogs = require('werelogs'); const werelogs = require('werelogs');
const errors = require('../../../errors'); const errors = require('../../../errors');
const jsutil = require('../../../jsutil');
class ListRecordStream extends stream.Transform { class ListRecordStream extends stream.Transform {
constructor(logger) { constructor(logger) {
@ -87,6 +88,7 @@ class LogConsumer {
readRecords(params, cb) { readRecords(params, cb) {
const recordStream = new ListRecordStream(this.logger); const recordStream = new ListRecordStream(this.logger);
const _params = params || {}; const _params = params || {};
const cbOnce = jsutil.once(cb);
this.bucketClient.getRaftLog( this.bucketClient.getRaftLog(
this.raftSession, _params.startSeq, _params.limit, this.raftSession, _params.startSeq, _params.limit,
@ -96,26 +98,26 @@ class LogConsumer {
// no such raft session, log and ignore // no such raft session, log and ignore
this.logger.warn('raft session does not exist yet', this.logger.warn('raft session does not exist yet',
{ raftId: this.raftSession }); { raftId: this.raftSession });
return cb(null, { info: { start: null, return cbOnce(null, { info: { start: null,
end: null } }); end: null } });
} }
if (err.code === 416) { if (err.code === 416) {
// requested range not satisfiable // requested range not satisfiable
this.logger.debug('no new log record to process', this.logger.debug('no new log record to process',
{ raftId: this.raftSession }); { raftId: this.raftSession });
return cb(null, { info: { start: null, return cbOnce(null, { info: { start: null,
end: null } }); end: null } });
} }
this.logger.error( this.logger.error(
'Error handling record log request', { error: err }); 'Error handling record log request', { error: err });
return cb(err); return cbOnce(err);
} }
// setup a temporary listener until the 'header' event // setup a temporary listener until the 'header' event
// is emitted // is emitted
recordStream.on('error', err => { recordStream.on('error', err => {
this.logger.error('error receiving raft log', this.logger.error('error receiving raft log',
{ error: err.message }); { error: err.message });
return cb(errors.InternalError); return cbOnce(errors.InternalError);
}); });
const jsonResponse = stream.pipe(jsonStream.parse('log.*')); const jsonResponse = stream.pipe(jsonStream.parse('log.*'));
jsonResponse.pipe(recordStream); jsonResponse.pipe(recordStream);
@ -124,7 +126,7 @@ class LogConsumer {
.on('header', header => { .on('header', header => {
// remove temporary listener // remove temporary listener
recordStream.removeAllListeners('error'); recordStream.removeAllListeners('error');
return cb(null, { info: header.info, return cbOnce(null, { info: header.info,
log: recordStream }); log: recordStream });
}) })
.on('error', err => recordStream.emit('error', err)); .on('error', err => recordStream.emit('error', err));

View File

@ -5,6 +5,7 @@ module.exports.VersioningConstants = {
DbPrefixes: { DbPrefixes: {
Master: '\x7fM', Master: '\x7fM',
Version: '\x7fV', Version: '\x7fV',
Replay: '\x7fR',
}, },
BucketVersioningKeyFormat: { BucketVersioningKeyFormat: {
current: 'v1', current: 'v1',

View File

@ -3,7 +3,7 @@
"engines": { "engines": {
"node": ">=6.9.5" "node": ">=6.9.5"
}, },
"version": "7.7.0", "version": "7.7.1",
"description": "Common utilities for the S3 project components", "description": "Common utilities for the S3 project components",
"main": "index.js", "main": "index.js",
"repository": { "repository": {

View File

@ -8,12 +8,14 @@ const {
FILTER_ACCEPT, FILTER_ACCEPT,
FILTER_SKIP, FILTER_SKIP,
SKIP_NONE, SKIP_NONE,
inc,
} = require('../../../../lib/algos/list/tools'); } = require('../../../../lib/algos/list/tools');
const VSConst = const VSConst =
require('../../../../lib/versioning/constants').VersioningConstants; require('../../../../lib/versioning/constants').VersioningConstants;
const Version = require('../../../../lib/versioning/Version').Version; const Version = require('../../../../lib/versioning/Version').Version;
const { generateVersionId } = require('../../../../lib/versioning/VersionID'); const { generateVersionId } = require('../../../../lib/versioning/VersionID');
const { DbPrefixes } = VSConst; const { DbPrefixes } = VSConst;
const zpad = require('../../helpers').zpad;
const VID_SEP = VSConst.VersionId.Separator; const VID_SEP = VSConst.VersionId.Separator;
@ -64,7 +66,6 @@ function getListingKey(key, vFormat) {
fakeLogger, vFormat); fakeLogger, vFormat);
/* Filter a master version to set NextMarker. */ /* Filter a master version to set NextMarker. */
// TODO: useless once S3C-1628 is fixed.
const listingKey = getListingKey(key, vFormat); const listingKey = getListingKey(key, vFormat);
delimiter.filter({ key: listingKey, value: '' }); delimiter.filter({ key: listingKey, value: '' });
assert.strictEqual(delimiter.NextMarker, key); assert.strictEqual(delimiter.NextMarker, key);
@ -215,8 +216,8 @@ function getListingKey(key, vFormat) {
value: Version.generatePHDVersion(generateVersionId('', '')), value: Version.generatePHDVersion(generateVersionId('', '')),
}; };
/* When filtered, it should return FILTER_ACCEPT and set the prvKey. /* When filtered, it should return FILTER_ACCEPT and set the prvKey
* to undefined. It should not be added to result the content or common * to undefined. It should not be added to the result content or common
* prefixes. */ * prefixes. */
assert.strictEqual(delimiter.filter(objPHD), FILTER_ACCEPT); assert.strictEqual(delimiter.filter(objPHD), FILTER_ACCEPT);
assert.strictEqual(delimiter.prvKey, undefined); assert.strictEqual(delimiter.prvKey, undefined);
@ -238,7 +239,7 @@ function getListingKey(key, vFormat) {
* and element in result content. */ * and element in result content. */
delimiter.filter({ key, value }); delimiter.filter({ key, value });
/* When filtered, it should return FILTER_ACCEPT and set the prvKey. /* When filtered, it should return FILTER_ACCEPT and set the prvKey
* to undefined. It should not be added to the result content or common * to undefined. It should not be added to the result content or common
* prefixes. */ * prefixes. */
assert.strictEqual(delimiter.filter(objPHD), FILTER_ACCEPT); assert.strictEqual(delimiter.filter(objPHD), FILTER_ACCEPT);
@ -283,7 +284,7 @@ function getListingKey(key, vFormat) {
}); });
}); });
it('should accept a delete marker', () => { it('should skip a delete marker version', () => {
const delimiter = new DelimiterMaster({}, fakeLogger, vFormat); const delimiter = new DelimiterMaster({}, fakeLogger, vFormat);
const version = new Version({ isDeleteMarker: true }); const version = new Version({ isDeleteMarker: true });
const key = 'key'; const key = 'key';
@ -300,7 +301,7 @@ function getListingKey(key, vFormat) {
assert.deepStrictEqual(delimiter.result(), EmptyResult); assert.deepStrictEqual(delimiter.result(), EmptyResult);
}); });
it('should skip version after a delete marker', () => { it('should skip version after a delete marker master', () => {
const delimiter = new DelimiterMaster({}, fakeLogger, vFormat); const delimiter = new DelimiterMaster({}, fakeLogger, vFormat);
const version = new Version({ isDeleteMarker: true }); const version = new Version({ isDeleteMarker: true });
const key = 'key'; const key = 'key';
@ -316,7 +317,7 @@ function getListingKey(key, vFormat) {
assert.deepStrictEqual(delimiter.result(), EmptyResult); assert.deepStrictEqual(delimiter.result(), EmptyResult);
}); });
it('should accept a new key after a delete marker', () => { it('should accept a new master key after a delete marker master', () => {
const delimiter = new DelimiterMaster({}, fakeLogger, vFormat); const delimiter = new DelimiterMaster({}, fakeLogger, vFormat);
const version = new Version({ isDeleteMarker: true }); const version = new Version({ isDeleteMarker: true });
const key1 = 'key1'; const key1 = 'key1';
@ -454,6 +455,39 @@ function getListingKey(key, vFormat) {
assert.strictEqual(delimiter.filter({ key, value }), FILTER_SKIP); assert.strictEqual(delimiter.filter({ key, value }), FILTER_SKIP);
}); });
it('should return good skipping value for DelimiterMaster on replay keys', () => {
const delimiter = new DelimiterMaster(
{ delimiter: '/', v2: true },
fakeLogger, vFormat);
for (let i = 0; i < 10; i++) {
delimiter.filter({
key: `foo/${zpad(i)}`,
value: '{}',
});
}
// simulate a listing that goes through a replay key, ...
assert.strictEqual(
delimiter.filter({
key: `${DbPrefixes.Replay}xyz`,
value: 'abcdef',
}),
FILTER_SKIP);
// ...it should skip the whole replay prefix
assert.strictEqual(delimiter.skipping(), DbPrefixes.Replay);
// simulate a listing that reaches regular object keys
// beyond the replay prefix, ...
assert.strictEqual(
delimiter.filter({
key: `${inc(DbPrefixes.Replay)}foo/bar`,
value: '{}',
}),
FILTER_ACCEPT);
// ...it should return to skipping by prefix as usual
assert.strictEqual(delimiter.skipping(), `${inc(DbPrefixes.Replay)}foo/`);
});
} }
}); });
}); });

View File

@ -3,14 +3,29 @@
const assert = require('assert'); const assert = require('assert');
const DelimiterVersions = const DelimiterVersions =
require('../../../../lib/algos/list/delimiterVersions').DelimiterVersions; require('../../../../lib/algos/list/delimiterVersions').DelimiterVersions;
const {
FILTER_ACCEPT,
FILTER_SKIP,
SKIP_NONE,
inc,
} = require('../../../../lib/algos/list/tools');
const Werelogs = require('werelogs').Logger; const Werelogs = require('werelogs').Logger;
const logger = new Werelogs('listTest'); const logger = new Werelogs('listTest');
const performListing = require('../../../utils/performListing'); const performListing = require('../../../utils/performListing');
const zpad = require('../../helpers').zpad; const zpad = require('../../helpers').zpad;
const { inc } = require('../../../../lib/algos/list/tools');
const VSConst = require('../../../../lib/versioning/constants').VersioningConstants; const VSConst = require('../../../../lib/versioning/constants').VersioningConstants;
const Version = require('../../../../lib/versioning/Version').Version;
const { generateVersionId } = require('../../../../lib/versioning/VersionID');
const { DbPrefixes } = VSConst; const { DbPrefixes } = VSConst;
const VID_SEP = VSConst.VersionId.Separator; const VID_SEP = VSConst.VersionId.Separator;
const EmptyResult = {
Versions: [],
CommonPrefixes: [],
IsTruncated: false,
NextKeyMarker: undefined,
NextVersionIdMarker: undefined,
Delimiter: undefined,
};
class Test { class Test {
constructor(name, input, genMDParams, output, filter) { constructor(name, input, genMDParams, output, filter) {
@ -264,7 +279,7 @@ const tests = [
NextKeyMarker: undefined, NextKeyMarker: undefined,
NextVersionIdMarker: undefined, NextVersionIdMarker: undefined,
}), }),
new Test('bad key marker and good prefix', { new Test('with bad key marker and good prefix', {
delimiter: '/', delimiter: '/',
prefix: 'notes/summer/', prefix: 'notes/summer/',
keyMarker: 'notes/summer0', keyMarker: 'notes/summer0',
@ -288,7 +303,7 @@ const tests = [
NextKeyMarker: undefined, NextKeyMarker: undefined,
NextVersionIdMarker: undefined, NextVersionIdMarker: undefined,
}, (e, input) => e.key > input.keyMarker), }, (e, input) => e.key > input.keyMarker),
new Test('delimiter and prefix (related to #147)', { new Test('with delimiter and prefix (related to #147)', {
delimiter: '/', delimiter: '/',
prefix: 'notes/', prefix: 'notes/',
}, { }, {
@ -318,7 +333,7 @@ const tests = [
NextKeyMarker: undefined, NextKeyMarker: undefined,
NextVersionIdMarker: undefined, NextVersionIdMarker: undefined,
}), }),
new Test('delimiter, prefix and marker (related to #147)', { new Test('with delimiter, prefix and marker (related to #147)', {
delimiter: '/', delimiter: '/',
prefix: 'notes/', prefix: 'notes/',
keyMarker: 'notes/year.txt', keyMarker: 'notes/year.txt',
@ -346,7 +361,7 @@ const tests = [
NextKeyMarker: undefined, NextKeyMarker: undefined,
NextVersionIdMarker: undefined, NextVersionIdMarker: undefined,
}, (e, input) => e.key > input.keyMarker), }, (e, input) => e.key > input.keyMarker),
new Test('all parameters 1/3', { new Test('with all parameters 1/3', {
delimiter: '/', delimiter: '/',
prefix: 'notes/', prefix: 'notes/',
keyMarker: 'notes/', keyMarker: 'notes/',
@ -372,7 +387,7 @@ const tests = [
NextVersionIdMarker: undefined, NextVersionIdMarker: undefined,
}, (e, input) => e.key > input.keyMarker), }, (e, input) => e.key > input.keyMarker),
new Test('all parameters 2/3', { new Test('with all parameters 2/3', {
delimiter: '/', delimiter: '/',
prefix: 'notes/', // prefix prefix: 'notes/', // prefix
keyMarker: 'notes/spring/', keyMarker: 'notes/spring/',
@ -398,7 +413,7 @@ const tests = [
NextVersionIdMarker: undefined, NextVersionIdMarker: undefined,
}, (e, input) => e.key > input.keyMarker), }, (e, input) => e.key > input.keyMarker),
new Test('all parameters 3/3', { new Test('with all parameters 3/3', {
delimiter: '/', delimiter: '/',
prefix: 'notes/', // prefix prefix: 'notes/', // prefix
keyMarker: 'notes/summer/', keyMarker: 'notes/summer/',
@ -426,7 +441,7 @@ const tests = [
NextVersionIdMarker: receivedData[19].versionId, NextVersionIdMarker: receivedData[19].versionId,
}, (e, input) => e.key > input.keyMarker), }, (e, input) => e.key > input.keyMarker),
new Test('all parameters 4/3', { new Test('with all parameters 4/3', {
delimiter: '/', delimiter: '/',
prefix: 'notes/', // prefix prefix: 'notes/', // prefix
keyMarker: 'notes/year.txt', keyMarker: 'notes/year.txt',
@ -454,7 +469,7 @@ const tests = [
NextVersionIdMarker: receivedData[20].versionId, NextVersionIdMarker: receivedData[20].versionId,
}, (e, input) => e.key > input.keyMarker), }, (e, input) => e.key > input.keyMarker),
new Test('all parameters 5/3', { new Test('with all parameters 5/3', {
delimiter: '/', delimiter: '/',
prefix: 'notes/', prefix: 'notes/',
keyMarker: 'notes/yore.rs', keyMarker: 'notes/yore.rs',
@ -481,39 +496,79 @@ const tests = [
}, (e, input) => e.key > input.keyMarker), }, (e, input) => e.key > input.keyMarker),
]; ];
function getListingKey(key, vFormat) {
if (vFormat === 'v0') {
return key;
}
if (vFormat === 'v1') {
const keyPrefix = key.includes(VID_SEP) ?
DbPrefixes.Version : DbPrefixes.Master;
return `${keyPrefix}${key}`;
}
return assert.fail(`bad format ${vFormat}`);
}
function getTestListing(test, data, vFormat) { function getTestListing(test, data, vFormat) {
return data return data
.filter(e => test.filter(e, test.input)) .filter(e => test.filter(e, test.input))
.map(e => { .map(e => ({
if (vFormat === 'v0') { key: getListingKey(e.key, vFormat),
return e; value: e.value,
} }));
if (vFormat === 'v1') {
const keyPrefix = e.key.includes(VID_SEP) ?
DbPrefixes.Version : DbPrefixes.Master;
return {
key: `${keyPrefix}${e.key}`,
value: e.value,
};
}
return assert.fail(`bad format ${vFormat}`);
});
} }
['v0', 'v1'].forEach(vFormat => { ['v0', 'v1'].forEach(vFormat => {
describe(`Delimiter All Versions listing algorithm vFormat=${vFormat}`, () => { describe(`Delimiter All Versions listing algorithm vFormat=${vFormat}`, () => {
it('Should return good skipping value for DelimiterVersions', () => { it('Should return good skipping value for DelimiterVersions', () => {
const delimiter = new DelimiterVersions({ delimiter: '/' }); const delimiter = new DelimiterVersions({ delimiter: '/' }, logger, vFormat);
for (let i = 0; i < 100; i++) { for (let i = 0; i < 100; i++) {
delimiter.filter({ delimiter.filter({
key: `${vFormat === 'v1' ? DbPrefixes.Master : ''}foo/${zpad(i)}`, key: `${vFormat === 'v1' ? DbPrefixes.Master : ''}foo/${zpad(i)}`,
value: '{}', value: '{}',
}); });
} }
assert.strictEqual(delimiter.skipping(), if (vFormat === 'v1') {
`${vFormat === 'v1' ? DbPrefixes.Master : ''}foo/`); assert.deepStrictEqual(delimiter.skipping(), [
`${DbPrefixes.Master}foo/`,
`${DbPrefixes.Version}foo/`,
]);
} else {
assert.strictEqual(delimiter.skipping(), 'foo/');
}
}); });
if (vFormat === 'v0') {
it('Should return good skipping value for DelimiterVersions on replay keys', () => {
const delimiter = new DelimiterVersions({ delimiter: '/' }, logger, vFormat);
for (let i = 0; i < 10; i++) {
delimiter.filter({
key: `foo/${zpad(i)}`,
value: '{}',
});
}
// simulate a listing that goes through a replay key, ...
assert.strictEqual(
delimiter.filter({
key: `${DbPrefixes.Replay}xyz`,
value: 'abcdef',
}),
FILTER_SKIP);
// ...it should skip the whole replay prefix
assert.strictEqual(delimiter.skipping(), DbPrefixes.Replay);
// simulate a listing that reaches regular object keys
// beyond the replay prefix, ...
assert.strictEqual(
delimiter.filter({
key: `${inc(DbPrefixes.Replay)}foo/bar`,
value: '{}',
}),
FILTER_ACCEPT);
// ...it should return to skipping by prefix as usual
assert.strictEqual(delimiter.skipping(), `${inc(DbPrefixes.Replay)}foo/`);
});
}
tests.forEach(test => { tests.forEach(test => {
it(`Should return metadata listing params to list ${test.name}`, () => { it(`Should return metadata listing params to list ${test.name}`, () => {
const listing = new DelimiterVersions(test.input, logger, vFormat); const listing = new DelimiterVersions(test.input, logger, vFormat);
@ -527,5 +582,442 @@ function getTestListing(test, data, vFormat) {
assert.deepStrictEqual(res, test.output); assert.deepStrictEqual(res, test.output);
}); });
}); });
it('skipping() should return SKIP_NONE when NextMarker is undefined', () => {
const delimiter = new DelimiterVersions({ delimiter: '/' }, logger, vFormat);
assert.strictEqual(delimiter.NextMarker, undefined);
assert.strictEqual(delimiter.skipping(), SKIP_NONE);
});
it('skipping() should return SKIP_NONE when marker is set and ' +
'does not contain the delimiter', () => {
const key = 'foo';
const delimiter = new DelimiterVersions({ delimiter: '/', marker: key },
logger, vFormat);
/* Filter a master version to set NextMarker. */
const listingKey = getListingKey(key, vFormat);
delimiter.filter({ key: listingKey, value: '' });
assert.strictEqual(delimiter.NextMarker, 'foo');
assert.strictEqual(delimiter.skipping(), SKIP_NONE);
});
it('skipping() should return prefix to skip when marker is set and ' +
'contains the delimiter', () => {
const key = 'foo/bar';
const delimiter = new DelimiterVersions({ delimiter: '/', marker: key },
logger, vFormat);
/* Filter a master version to set NextMarker. */
const listingKey = getListingKey(key, vFormat);
delimiter.filter({ key: listingKey, value: '' });
assert.strictEqual(delimiter.NextMarker, 'foo/');
if (vFormat === 'v0') {
assert.strictEqual(delimiter.skipping(), 'foo/');
} else {
assert.deepStrictEqual(delimiter.skipping(), [
`${DbPrefixes.Master}foo/`,
`${DbPrefixes.Version}foo/`,
]);
}
});
it('skipping() should return prefix when marker is set and ' +
'ends with the delimiter', () => {
const key = 'foo/';
const delimiter = new DelimiterVersions({ delimiter: '/', marker: key },
logger, vFormat);
/* Filter a master version to set NextMarker. */
const listingKey = getListingKey(key, vFormat);
delimiter.filter({ key: listingKey, value: '' });
assert.strictEqual(delimiter.NextMarker, 'foo/');
if (vFormat === 'v0') {
assert.strictEqual(delimiter.skipping(), 'foo/');
} else {
assert.deepStrictEqual(delimiter.skipping(), [
`${DbPrefixes.Master}foo/`,
`${DbPrefixes.Version}foo/`,
]);
}
});
it('should skip entries not starting with prefix', () => {
const delimiter = new DelimiterVersions({ prefix: 'prefix' }, logger, vFormat);
const listingKey = getListingKey('wrong', vFormat);
assert.strictEqual(delimiter.filter({ key: listingKey, value: '' }), FILTER_SKIP);
assert.strictEqual(delimiter.NextMarker, undefined);
assert.strictEqual(delimiter.prvKey, undefined);
assert.deepStrictEqual(delimiter.result(), EmptyResult);
});
it('should accept a master version', () => {
const delimiter = new DelimiterVersions({}, logger, vFormat);
const key = 'key';
const value = '';
const listingKey = getListingKey(key, vFormat);
assert.strictEqual(delimiter.filter({ key: listingKey, value }), FILTER_ACCEPT);
assert.strictEqual(delimiter.NextMarker, key);
assert.deepStrictEqual(delimiter.result(), {
CommonPrefixes: [],
Versions: [{
key: 'key',
value: '',
versionId: 'null',
}],
Delimiter: undefined,
IsTruncated: false,
NextKeyMarker: undefined,
NextVersionIdMarker: undefined,
});
});
it('should return good values for entries with different common prefixes', () => {
const delimiter = new DelimiterVersions({ delimiter: '/' },
logger, vFormat);
/* Filter the first entry with a common prefix. It should be
* accepted and added to the result. */
assert.strictEqual(delimiter.filter({
key: getListingKey('commonPrefix1/key1', vFormat),
value: '',
}),
FILTER_ACCEPT);
assert.deepStrictEqual(delimiter.result(), {
CommonPrefixes: ['commonPrefix1/'],
Versions: [],
Delimiter: '/',
IsTruncated: false,
NextKeyMarker: undefined,
NextVersionIdMarker: undefined,
});
/* Filter the second entry with the same common prefix than the
* first entry. It should be skipped and not added to the result. */
assert.strictEqual(delimiter.filter({
key: getListingKey('commonPrefix1/key2', vFormat),
value: '',
}),
FILTER_SKIP);
assert.deepStrictEqual(delimiter.result(), {
CommonPrefixes: ['commonPrefix1/'],
Versions: [],
Delimiter: '/',
IsTruncated: false,
NextKeyMarker: undefined,
NextVersionIdMarker: undefined,
});
/* Filter an entry with a new common prefix. It should be accepted
* and not added to the result. */
assert.strictEqual(delimiter.filter({
key: getListingKey('commonPrefix2/key1', vFormat),
value: '',
}),
FILTER_ACCEPT);
assert.deepStrictEqual(delimiter.result(), {
CommonPrefixes: ['commonPrefix1/', 'commonPrefix2/'],
Versions: [],
Delimiter: '/',
IsTruncated: false,
NextKeyMarker: undefined,
NextVersionIdMarker: undefined,
});
});
it('should accept a delete marker version', () => {
const delimiter = new DelimiterVersions({}, logger, vFormat);
const version = new Version({ isDeleteMarker: true });
const key = 'key';
const obj = {
key: getListingKey(`${key}${VID_SEP}version`, vFormat),
value: version.toString(),
};
/* When filtered, it should return FILTER_ACCEPT and
* should be added to the result content. */
assert.strictEqual(delimiter.filter(obj), FILTER_ACCEPT);
assert.strictEqual(delimiter.NextMarker, key);
assert.deepStrictEqual(delimiter.result(), {
CommonPrefixes: [],
Versions: [{
key: 'key',
value: version.toString(),
versionId: 'version',
}],
Delimiter: undefined,
IsTruncated: false,
NextKeyMarker: undefined,
NextVersionIdMarker: undefined,
});
});
it('should accept a version after a delete marker master', () => {
const delimiter = new DelimiterVersions({}, logger, vFormat);
const version = new Version({ isDeleteMarker: true });
const key = 'key';
const versionKey = `${key}${VID_SEP}version`;
delimiter.filter({ key: getListingKey(key, vFormat), value: version.toString() });
assert.strictEqual(delimiter.filter({
key: getListingKey(versionKey, vFormat),
value: 'value',
}), FILTER_ACCEPT);
assert.strictEqual(delimiter.NextMarker, key);
assert.deepStrictEqual(delimiter.result(), {
CommonPrefixes: [],
Versions: [{
key: 'key',
value: version.toString(),
versionId: 'null',
}, {
key: 'key',
value: 'value',
versionId: 'version',
}],
Delimiter: undefined,
IsTruncated: false,
NextKeyMarker: undefined,
NextVersionIdMarker: undefined,
});
});
it('should accept a new master key w/ version after a delete marker master', () => {
const delimiter = new DelimiterVersions({}, logger, vFormat);
const version = new Version({ isDeleteMarker: true });
const key1 = 'key1';
const key2 = 'key2';
const value2 = '{"versionId":"version"}';
assert.strictEqual(delimiter.filter({
key: getListingKey(key1, vFormat),
value: version.toString(),
}), FILTER_ACCEPT);
assert.strictEqual(delimiter.filter({
key: getListingKey(key2, vFormat),
value: value2,
}), FILTER_ACCEPT);
assert.strictEqual(delimiter.NextMarker, key2);
assert.deepStrictEqual(delimiter.result(), {
CommonPrefixes: [],
Delimiter: undefined,
IsTruncated: false,
NextKeyMarker: undefined,
NextVersionIdMarker: undefined,
Versions: [{
key: 'key1',
value: '{"isDeleteMarker":true}',
versionId: 'null',
}, {
key: 'key2',
value: '{"versionId":"version"}',
versionId: 'version',
}],
});
});
it('should accept a version after skipping an object because of its commonPrefix', () => {
const commonPrefix1 = 'commonPrefix1/';
const commonPrefix2 = 'commonPrefix2/';
const prefix1Key1 = 'commonPrefix1/key1';
const prefix1Key2 = 'commonPrefix1/key2';
const prefix2VersionKey1 = `commonPrefix2/key1${VID_SEP}version`;
const value = '{"versionId":"version"}';
const delimiter = new DelimiterVersions({ delimiter: '/' },
logger, vFormat);
/* Filter the two first entries with the same common prefix to add
* it to the result and reach the state where an entry is skipped
* because of an already present common prefix in the result. */
delimiter.filter({ key: getListingKey(prefix1Key1, vFormat), value });
delimiter.filter({ key: getListingKey(prefix1Key2, vFormat), value });
/* Filter an object with a key containing a version part and a new
* common prefix. It should be accepted and the new common prefix
* added to the result. */
assert.strictEqual(delimiter.filter({
key: getListingKey(prefix2VersionKey1, vFormat),
value,
}), FILTER_ACCEPT);
assert.deepStrictEqual(delimiter.result(), {
CommonPrefixes: [commonPrefix1, commonPrefix2],
Versions: [],
Delimiter: '/',
IsTruncated: false,
NextKeyMarker: undefined,
NextVersionIdMarker: undefined,
});
});
it('should skip first version key if equal to master', () => {
const delimiter = new DelimiterVersions({}, logger, vFormat);
const masterKey = 'key';
const versionKey1 = `${masterKey}${VID_SEP}version1`;
const versionKey2 = `${masterKey}${VID_SEP}version2`;
const value2 = 'value2';
/* Filter the master version for version1 */
assert.strictEqual(delimiter.filter({
key: getListingKey(masterKey, vFormat),
value: '{"versionId":"version1"}',
}), FILTER_ACCEPT);
/* Filter the version key for version1 */
assert.strictEqual(delimiter.filter({
key: getListingKey(versionKey1, vFormat),
value: '{"versionId":"version1"}',
}), FILTER_SKIP);
/* Filter the version key for version2 */
assert.strictEqual(delimiter.filter({
key: getListingKey(versionKey2, vFormat),
value: value2,
}), FILTER_ACCEPT);
assert.deepStrictEqual(delimiter.result(), {
CommonPrefixes: [],
Versions: [{
key: 'key',
value: '{"versionId":"version1"}',
versionId: 'version1',
}, {
key: 'key',
value: 'value2',
versionId: 'version2',
}],
Delimiter: undefined,
IsTruncated: false,
NextKeyMarker: undefined,
NextVersionIdMarker: undefined,
});
});
it('should skip master and version key if under a known prefix', () => {
const commonPrefix1 = 'commonPrefix/';
const prefixKey1 = 'commonPrefix/key1';
const prefixKey2 = 'commonPrefix/key2';
const prefixVersionKey1 = `commonPrefix/key2${VID_SEP}version`;
const value = '{"versionId":"version"}';
const delimiter = new DelimiterVersions({ delimiter: '/' },
logger, vFormat);
assert.strictEqual(delimiter.filter({
key: getListingKey(prefixKey1, vFormat),
value,
}), FILTER_ACCEPT);
/* The second master key of the same common prefix should be skipped */
assert.strictEqual(delimiter.filter({
key: getListingKey(prefixKey2, vFormat),
value,
}), FILTER_SKIP);
/* The version key of the same common prefix should also be skipped */
assert.strictEqual(delimiter.filter({
key: getListingKey(prefixVersionKey1, vFormat),
value,
}), FILTER_SKIP);
assert.deepStrictEqual(delimiter.result(), {
CommonPrefixes: [commonPrefix1],
Versions: [],
Delimiter: '/',
IsTruncated: false,
NextKeyMarker: undefined,
NextVersionIdMarker: undefined,
});
});
if (vFormat === 'v0') {
it('should accept a PHD version as first input', () => {
const delimiter = new DelimiterVersions({}, logger, vFormat);
const keyPHD = 'keyPHD';
const objPHD = {
key: keyPHD,
value: Version.generatePHDVersion(generateVersionId('', '')),
};
/* When filtered, it should return FILTER_ACCEPT and set the prvKey
* to undefined. It should not be added to the result content or common
* prefixes. */
assert.strictEqual(delimiter.filter(objPHD), FILTER_ACCEPT);
assert.strictEqual(delimiter.prvKey, undefined);
assert.strictEqual(delimiter.NextMarker, undefined);
assert.deepStrictEqual(delimiter.result(), EmptyResult);
});
it('should accept a PHD version', () => {
const delimiter = new DelimiterVersions({}, logger, vFormat);
const key = 'keyA';
const value = '';
const keyPHD = 'keyBPHD';
const objPHD = {
key: keyPHD,
value: Version.generatePHDVersion(generateVersionId('', '')),
};
/* Filter a master version to set the NextMarker and add
* and element in result content. */
delimiter.filter({ key, value });
/* When filtered, it should return FILTER_ACCEPT. It
* should not be added to the result content or common
* prefixes. */
assert.strictEqual(delimiter.filter(objPHD), FILTER_ACCEPT);
assert.strictEqual(delimiter.prvKey, undefined);
assert.strictEqual(delimiter.NextMarker, key);
assert.deepStrictEqual(delimiter.result(), {
CommonPrefixes: [],
Versions: [{
key: 'keyA',
value: '',
versionId: 'null',
}],
Delimiter: undefined,
IsTruncated: false,
NextKeyMarker: undefined,
NextVersionIdMarker: undefined,
});
});
it('should accept a version after a PHD', () => {
const delimiter = new DelimiterVersions({}, logger, vFormat);
const masterKey = 'key';
const keyVersion = `${masterKey}${VID_SEP}version`;
const value = '';
const objPHD = {
key: masterKey,
value: Version.generatePHDVersion(generateVersionId('', '')),
};
/* Filter the PHD object. */
delimiter.filter(objPHD);
/* The filtering of the PHD object has no impact, the version is
* accepted and added to the result. */
assert.strictEqual(delimiter.filter({
key: keyVersion,
value,
}), FILTER_ACCEPT);
assert.strictEqual(delimiter.NextMarker, masterKey);
assert.deepStrictEqual(delimiter.result(), {
CommonPrefixes: [],
Versions: [{
key: 'key',
value: '',
versionId: 'version',
}],
Delimiter: undefined,
IsTruncated: false,
NextKeyMarker: undefined,
NextVersionIdMarker: undefined,
});
});
}
}); });
}); });

View File

@ -69,6 +69,8 @@ describe('ObjectMD class setters/getters', () => {
['IsNull', true], ['IsNull', true],
['NullVersionId', null, undefined], ['NullVersionId', null, undefined],
['NullVersionId', '111111'], ['NullVersionId', '111111'],
['NullUploadId', null, undefined],
['NullUploadId', 'abcdefghi'],
['IsDeleteMarker', null, false], ['IsDeleteMarker', null, false],
['IsDeleteMarker', true], ['IsDeleteMarker', true],
['VersionId', null, undefined], ['VersionId', null, undefined],
@ -78,6 +80,8 @@ describe('ObjectMD class setters/getters', () => {
key: 'value', key: 'value',
}], }],
['Tags', null, {}], ['Tags', null, {}],
['UploadId', null, undefined],
['UploadId', 'abcdefghi'],
['ReplicationInfo', null, { ['ReplicationInfo', null, {
status: '', status: '',
backends: [], backends: [],
@ -332,9 +336,11 @@ describe('getAttributes static method', () => {
'location': true, 'location': true,
'isNull': true, 'isNull': true,
'nullVersionId': true, 'nullVersionId': true,
'nullUploadId': true,
'isDeleteMarker': true, 'isDeleteMarker': true,
'versionId': true, 'versionId': true,
'tags': true, 'tags': true,
'uploadId': true,
'replicationInfo': true, 'replicationInfo': true,
'dataStoreName': true, 'dataStoreName': true,
'last-modified': true, 'last-modified': true,