Skip to content

Commit eb4bec3

Browse files
committed
test: namespaced client tests and fixes
1 parent a8c7254 commit eb4bec3

File tree

2 files changed

+354
-2
lines changed

2 files changed

+354
-2
lines changed

lib/namespaced.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,16 @@ export const Namespaced = (bucketPrefix) => {
253253
bucketExists: (minio) => {
254254
const client = { getMeta: getMeta(minio) }
255255

256-
return (name) => client.getMeta().chain((meta) => checkNamespaceExists(meta, name))
256+
return (name) =>
257+
client.getMeta()
258+
.chain((meta) => checkNamespaceExists(meta, name))
259+
.bichain(
260+
(err) => {
261+
if (isHyperErr(err) && err.status === 404) return Async.Resolved(false)
262+
return Async.Rejected(err)
263+
},
264+
Async.Resolved,
265+
)
257266
},
258267
listBuckets: (minio) => {
259268
const client = { getMeta: getMeta(minio) }
@@ -311,7 +320,12 @@ export const Namespaced = (bucketPrefix) => {
311320
/**
312321
* trim the namespace off of the object name
313322
*/
314-
.map((objects) => objects.map((o) => ({ ...o, name: o.name.substring(bucket.length) })))
323+
.map((objects) =>
324+
objects.map((o) => ({
325+
...o,
326+
name: o.name.substring(createPrefix(bucket, prefix).length + 1),
327+
}))
328+
)
315329
},
316330
}
317331
}

lib/namespaced.test.js

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
import { crocks, ReadableWebToNodeStream } from '../deps.js'
2+
import { assert, assertEquals, assertObjectMatch } from '../dev_deps.js'
3+
4+
import { Namespaced } from './namespaced.js'
5+
6+
const { Async } = crocks
7+
8+
Deno.test('Namespaced bucket minio client', async (t) => {
9+
const namespaced = Namespaced('test')
10+
11+
const META = {
12+
createdAt: new Date().toJSON(),
13+
foo: {
14+
createdAt: new Date().toJSON(),
15+
},
16+
deleted: {
17+
createdAt: new Date().toJSON(),
18+
deletedAt: new Date().toJSON(),
19+
},
20+
}
21+
const happyMinio = {
22+
makeBucket: () => Promise.resolve(),
23+
getObject: (_bucket, key) => {
24+
if (key === 'meta.json') {
25+
return Promise.resolve(
26+
new ReadableWebToNodeStream(
27+
new Response(JSON.stringify(META)).body,
28+
),
29+
)
30+
}
31+
32+
return Promise.resolve(
33+
new ReadableWebToNodeStream(new Response(JSON.stringify({ fizz: 'buzz' })).body),
34+
)
35+
},
36+
bucketExists: () => Promise.resolve(true),
37+
putObject: () => Promise.resolve(),
38+
}
39+
40+
await t.step('makeBucket', async (t) => {
41+
await t.step('it should resolve if the namespace is created successfully', () => {
42+
return namespaced.makeBucket(happyMinio)('new_bucket')
43+
.map(() => assert(true))
44+
.toPromise()
45+
})
46+
47+
await t.step(
48+
'it should create a single bucket to namespace if it does not already exist',
49+
() => {
50+
return namespaced.makeBucket({
51+
...happyMinio,
52+
makeBucket: (name) => {
53+
assertEquals(name, 'test-namespaced')
54+
return Promise.resolve()
55+
},
56+
bucketExists: (name) => {
57+
assertEquals(name, 'test-namespaced')
58+
return Promise.resolve(false)
59+
},
60+
})('new_bucket')
61+
.toPromise()
62+
},
63+
)
64+
65+
await t.step(
66+
'it should create a metadata file if it does not already exist',
67+
() => {
68+
let putObjectCount = 0
69+
return namespaced.makeBucket({
70+
...happyMinio,
71+
getObject: (_bucket, key) => {
72+
if (key === 'meta.json') {
73+
return Promise.reject({ ok: false, status: 404 })
74+
}
75+
76+
return Promise.resolve(
77+
new ReadableWebToNodeStream(new Response(JSON.stringify({ fizz: 'buzz' })).body),
78+
)
79+
},
80+
putObject: async (_bucket, _key, body) => {
81+
putObjectCount++
82+
const meta = await (new Response(ReadableStream.from(body)).json())
83+
// Have to create the meta object, then persist the new bucket to it. separate calls
84+
if (putObjectCount > 1) assert(meta.new_bucket.createdAt)
85+
},
86+
})('new_bucket')
87+
.toPromise()
88+
},
89+
)
90+
91+
await t.step('it should update the metadata file with the new bucket metadata', () => {
92+
return namespaced.makeBucket({
93+
...happyMinio,
94+
putObject: async (_bucket, _key, body) => {
95+
const meta = await (new Response(ReadableStream.from(body)).json())
96+
assertObjectMatch(meta, META)
97+
assert(meta.new_bucket.createdAt)
98+
},
99+
})('new_bucket')
100+
.toPromise()
101+
})
102+
103+
await t.step('it should throw a HyperErr if the namespace already exists', () => {
104+
return namespaced.makeBucket(happyMinio)('foo')
105+
.bichain(
106+
(err) => {
107+
assertObjectMatch(err, { status: 409, msg: 'bucket already exists' })
108+
return Async.Resolved()
109+
},
110+
Async.Rejected,
111+
)
112+
.toPromise()
113+
})
114+
})
115+
116+
await t.step('removeBucket', async (t) => {
117+
const _happyMinio = {
118+
...happyMinio,
119+
listObjects: (bucket, prefix) => {
120+
assertEquals(bucket, 'test-namespaced')
121+
assertEquals(prefix, 'foo')
122+
123+
// Mock stream
124+
return {
125+
on: (event, fn) => {
126+
switch (event) {
127+
case 'data': {
128+
fn({ name: 'crap/fizz.png' })
129+
fn({ name: 'crap/bar.png' })
130+
break
131+
}
132+
case 'end': {
133+
return fn('foo')
134+
}
135+
}
136+
},
137+
}
138+
},
139+
removeObjects: () => Promise.resolve(),
140+
}
141+
142+
await t.step('should remove the namespace', () => {
143+
return namespaced.removeBucket({
144+
..._happyMinio,
145+
})('foo')
146+
.map(() => assert(true))
147+
.toPromise()
148+
})
149+
150+
await t.step('should update the metadata file', () => {
151+
return namespaced.removeBucket({
152+
..._happyMinio,
153+
listObjects: () => {
154+
// Mock stream
155+
return {
156+
on: (event, fn) => {
157+
switch (event) {
158+
case 'data': {
159+
break
160+
}
161+
case 'end': {
162+
return fn('foo')
163+
}
164+
}
165+
},
166+
}
167+
},
168+
putObject: async (_bucket, _key, body) => {
169+
const meta = await (new Response(ReadableStream.from(body)).json())
170+
assertObjectMatch(meta, META)
171+
assert(meta.foo.deletedAt)
172+
},
173+
})('foo')
174+
.toPromise()
175+
})
176+
177+
await t.step('should remove all objects in the namespace', () => {
178+
return namespaced.removeBucket({
179+
..._happyMinio,
180+
removeObjects: (bucket, keys) => {
181+
assertEquals(bucket, 'test-namespaced')
182+
assertEquals(keys, ['foo/crap/fizz.png', 'foo/crap/bar.png'])
183+
184+
return Promise.resolve()
185+
},
186+
})('foo')
187+
.map(() => assert(true))
188+
.toPromise()
189+
})
190+
})
191+
192+
await t.step('bucketExists', async (t) => {
193+
await t.step('should return true if the namespace exists', async () => {
194+
await namespaced.bucketExists(happyMinio)('foo')
195+
.map(assert)
196+
.toPromise()
197+
})
198+
199+
await t.step('should return false if the namespace does not exist', async () => {
200+
await namespaced.bucketExists(happyMinio)('new_bucket')
201+
.map((exists) => assert(!exists))
202+
.toPromise()
203+
})
204+
})
205+
206+
await t.step('listBuckets', async (t) => {
207+
await t.step('should return the bucket names', () => {
208+
return namespaced.listBuckets(happyMinio)()
209+
.map((res) => assertObjectMatch(res, ['foo']))
210+
.toPromise()
211+
})
212+
})
213+
214+
await t.step('putObject', async (t) => {
215+
await t.step('should put the object in the namespace', () => {
216+
return namespaced.putObject({
217+
...happyMinio,
218+
putObject: (bucket, key, body) => {
219+
assertEquals(bucket, 'test-namespaced')
220+
assertEquals(key, 'foo/bar/fizz.png')
221+
assert(body)
222+
return Promise.resolve()
223+
},
224+
})({
225+
bucket: 'foo',
226+
key: '/bar/fizz.png',
227+
body: new Response(JSON.stringify({ foo: 'bar' })).body,
228+
})
229+
.toPromise()
230+
})
231+
})
232+
233+
await t.step('removeObject', async (t) => {
234+
await t.step('should remove the object in the namespace', () => {
235+
return namespaced.removeObject({
236+
...happyMinio,
237+
removeObject: (bucket, key) => {
238+
assertEquals(bucket, 'test-namespaced')
239+
assertEquals(key, 'foo/bar/fizz.png')
240+
return Promise.resolve()
241+
},
242+
})({
243+
bucket: 'foo',
244+
key: '/bar/fizz.png',
245+
})
246+
.toPromise()
247+
})
248+
})
249+
250+
await t.step('removeObjects', async (t) => {
251+
await t.step('should remove the objects in the namespace', () => {
252+
return namespaced.removeObjects({
253+
...happyMinio,
254+
removeObjects: (bucket, keys) => {
255+
assertEquals(bucket, 'test-namespaced')
256+
assertEquals(keys, ['foo/bar/fizz.png', 'foo/fuzz.png'])
257+
return Promise.resolve()
258+
},
259+
})({
260+
bucket: 'foo',
261+
keys: ['bar/fizz.png', 'fuzz.png'],
262+
})
263+
.toPromise()
264+
})
265+
})
266+
267+
await t.step('getObject', async (t) => {
268+
await t.step('should get the object in the namespace', () => {
269+
return namespaced.getObject({
270+
...happyMinio,
271+
getObject: (bucket, key) => {
272+
assertEquals(bucket, 'test-namespaced')
273+
assertEquals(key, 'foo/bar/fizz.png')
274+
return Promise.resolve(new ReadableWebToNodeStream(new Response('foo').body))
275+
},
276+
})({
277+
bucket: 'foo',
278+
key: 'bar/fizz.png',
279+
})
280+
.toPromise()
281+
})
282+
})
283+
284+
await t.step('getSignedUrl', async (t) => {
285+
await t.step('should get a signed url in the namespace', () => {
286+
return namespaced.getSignedUrl({
287+
...happyMinio,
288+
presignedUrl: (method, bucket, key, expires) => {
289+
assertEquals(method, 'GET')
290+
// lowercase
291+
assertEquals(bucket, 'test-namespaced')
292+
assertEquals(key, 'FOO/bar.png')
293+
assertEquals(expires, 60 * 5)
294+
return Promise.resolve('http://presigned')
295+
},
296+
})({
297+
bucket: 'FOO',
298+
key: 'bar.png',
299+
method: 'GET',
300+
})
301+
.toPromise()
302+
})
303+
})
304+
305+
await t.step('listObjects', async (t) => {
306+
await t.step('should gather all of the objects in the namespace', () => {
307+
return namespaced.listObjects({
308+
listObjects: (bucket, prefix) => {
309+
assertEquals(bucket, 'test-namespaced')
310+
assertEquals(prefix, 'foo/crap')
311+
312+
// Mock stream
313+
return {
314+
on: (event, fn) => {
315+
switch (event) {
316+
case 'data': {
317+
fn({ name: 'foo/crap/fizz.png' })
318+
fn({ name: 'foo/crap/bar.png' })
319+
break
320+
}
321+
case 'end': {
322+
return fn('foo')
323+
}
324+
}
325+
},
326+
}
327+
},
328+
})({
329+
bucket: 'foo',
330+
prefix: 'crap',
331+
})
332+
.map((res) => {
333+
assertObjectMatch(res, [{ name: 'fizz.png' }, { name: 'bar.png' }])
334+
})
335+
.toPromise()
336+
})
337+
})
338+
})

0 commit comments

Comments
 (0)