Skip to content

Commit 5941642

Browse files
committed
feat: support list() in local server
1 parent aee17cf commit 5941642

File tree

4 files changed

+337
-109
lines changed

4 files changed

+337
-109
lines changed

src/metadata.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,12 @@ export const encodeMetadata = (metadata?: Metadata) => {
2222
return payload
2323
}
2424

25-
export const decodeMetadata = (headers?: Headers): Metadata => {
26-
if (!headers) {
25+
export const decodeMetadata = (header: string | null): Metadata => {
26+
if (!header || !header.startsWith(BASE64_PREFIX)) {
2727
return {}
2828
}
2929

30-
const metadataHeader = headers.get(METADATA_HEADER_INTERNAL)
31-
32-
if (!metadataHeader || !metadataHeader.startsWith(BASE64_PREFIX)) {
33-
return {}
34-
}
35-
36-
const encodedData = metadataHeader.slice(BASE64_PREFIX.length)
30+
const encodedData = header.slice(BASE64_PREFIX.length)
3731
const decodedData = Buffer.from(encodedData, 'base64').toString()
3832
const metadata = JSON.parse(decodedData)
3933

src/server.test.ts

Lines changed: 175 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { env, version as nodeVersion } from 'node:process'
33

44
import semver from 'semver'
55
import tmp from 'tmp-promise'
6-
import { describe, test, expect, beforeAll, afterEach } from 'vitest'
6+
import { test, expect, beforeAll, afterEach } from 'vitest'
77

88
import { getStore } from './main.js'
99
import { BlobsServer } from './server.js'
@@ -31,84 +31,183 @@ const siteID = '9a003659-aaaa-0000-aaaa-63d3720d8621'
3131
const key = '54321'
3232
const token = 'my-very-secret-token'
3333

34-
describe('Local server', () => {
35-
test('Reads and writes from the file system', async () => {
36-
const directory = await tmp.dir()
37-
const server = new BlobsServer({
38-
directory: directory.path,
39-
token,
40-
})
41-
const { port } = await server.start()
42-
const blobs = getStore({
43-
edgeURL: `http://localhost:${port}`,
44-
name: 'mystore',
45-
token,
46-
siteID,
47-
})
48-
49-
await blobs.set(key, 'value 1')
50-
expect(await blobs.get(key)).toBe('value 1')
51-
52-
await blobs.set(key, 'value 2')
53-
expect(await blobs.get(key)).toBe('value 2')
54-
55-
await blobs.delete(key)
56-
expect(await blobs.get(key)).toBe(null)
57-
58-
await server.stop()
59-
await fs.rm(directory.path, { force: true, recursive: true })
34+
test('Reads and writes from the file system', async () => {
35+
const directory = await tmp.dir()
36+
const server = new BlobsServer({
37+
directory: directory.path,
38+
token,
39+
})
40+
const { port } = await server.start()
41+
const blobs = getStore({
42+
edgeURL: `http://localhost:${port}`,
43+
name: 'mystore',
44+
token,
45+
siteID,
46+
})
47+
const metadata = {
48+
features: {
49+
blobs: true,
50+
functions: true,
51+
},
52+
name: 'Netlify',
53+
}
54+
55+
await blobs.set(key, 'value 1')
56+
expect(await blobs.get(key)).toBe('value 1')
57+
58+
await blobs.set(key, 'value 2', { metadata })
59+
expect(await blobs.get(key)).toBe('value 2')
60+
61+
const entry = await blobs.getWithMetadata(key)
62+
expect(entry.metadata).toEqual(metadata)
63+
64+
await blobs.delete(key)
65+
expect(await blobs.get(key)).toBe(null)
66+
67+
await server.stop()
68+
await fs.rm(directory.path, { force: true, recursive: true })
69+
})
70+
71+
test('Separates keys from different stores', async () => {
72+
const directory = await tmp.dir()
73+
const server = new BlobsServer({
74+
directory: directory.path,
75+
token,
76+
})
77+
const { port } = await server.start()
78+
79+
const store1 = getStore({
80+
edgeURL: `http://localhost:${port}`,
81+
name: 'mystore1',
82+
token,
83+
siteID,
84+
})
85+
const store2 = getStore({
86+
edgeURL: `http://localhost:${port}`,
87+
name: 'mystore2',
88+
token,
89+
siteID,
6090
})
6191

62-
test('Separates keys from different stores', async () => {
63-
const directory = await tmp.dir()
64-
const server = new BlobsServer({
65-
directory: directory.path,
66-
token,
67-
})
68-
const { port } = await server.start()
69-
70-
const store1 = getStore({
71-
edgeURL: `http://localhost:${port}`,
72-
name: 'mystore1',
73-
token,
74-
siteID,
75-
})
76-
const store2 = getStore({
77-
edgeURL: `http://localhost:${port}`,
78-
name: 'mystore2',
79-
token,
80-
siteID,
81-
})
82-
83-
await store1.set(key, 'value 1 for store 1')
84-
await store2.set(key, 'value 1 for store 2')
85-
86-
expect(await store1.get(key)).toBe('value 1 for store 1')
87-
expect(await store2.get(key)).toBe('value 1 for store 2')
88-
89-
await server.stop()
90-
await fs.rm(directory.path, { force: true, recursive: true })
92+
await store1.set(key, 'value 1 for store 1')
93+
await store2.set(key, 'value 1 for store 2')
94+
95+
expect(await store1.get(key)).toBe('value 1 for store 1')
96+
expect(await store2.get(key)).toBe('value 1 for store 2')
97+
98+
await server.stop()
99+
await fs.rm(directory.path, { force: true, recursive: true })
100+
})
101+
102+
test('If a token is set, rejects any requests with an invalid `authorization` header', async () => {
103+
const directory = await tmp.dir()
104+
const server = new BlobsServer({
105+
directory: directory.path,
106+
token,
91107
})
108+
const { port } = await server.start()
109+
const blobs = getStore({
110+
edgeURL: `http://localhost:${port}`,
111+
name: 'mystore',
112+
token: 'another token',
113+
siteID,
114+
})
115+
116+
await expect(async () => await blobs.get(key)).rejects.toThrowError(
117+
'Netlify Blobs has generated an internal error: 403 response',
118+
)
92119

93-
test('If a token is set, rejects any requests with an invalid `authorization` header', async () => {
94-
const directory = await tmp.dir()
95-
const server = new BlobsServer({
96-
directory: directory.path,
97-
token,
98-
})
99-
const { port } = await server.start()
100-
const blobs = getStore({
101-
edgeURL: `http://localhost:${port}`,
102-
name: 'mystore',
103-
token: 'another token',
104-
siteID,
105-
})
106-
107-
await expect(async () => await blobs.get(key)).rejects.toThrowError(
108-
'Netlify Blobs has generated an internal error: 403 response',
109-
)
110-
111-
await server.stop()
112-
await fs.rm(directory.path, { force: true, recursive: true })
120+
await server.stop()
121+
await fs.rm(directory.path, { force: true, recursive: true })
122+
})
123+
124+
test('Lists entries', async () => {
125+
const directory = await tmp.dir()
126+
const server = new BlobsServer({
127+
directory: directory.path,
128+
token,
129+
})
130+
const { port } = await server.start()
131+
const blobs = getStore({
132+
edgeURL: `http://localhost:${port}`,
133+
name: 'mystore',
134+
token,
135+
siteID,
113136
})
137+
const songs: Record<string, string> = {
138+
'coldplay/parachutes/shiver': "I'll always be waiting for you",
139+
'coldplay/parachutes/spies': 'And the spies came out of the water',
140+
'coldplay/parachutes/trouble': 'And I:I never meant to cause you trouble',
141+
'coldplay/a-rush-of-blood-to-the-head/politik': 'Give me heart and give me soul',
142+
'coldplay/a-rush-of-blood-to-the-head/in-my-place': 'How long must you wait for it?',
143+
'coldplay/a-rush-of-blood-to-the-head/the-scientist': 'Questions of science, science and progress',
144+
'phoenix/united/too-young': "Oh rainfalls and hard times coming they won't leave me tonight",
145+
'phoenix/united/party-time': 'Summertime is gone',
146+
'phoenix/ti-amo/j-boy': 'Something in the middle of the side of the store',
147+
'phoenix/ti-amo/fleur-de-lys': 'No rest till I get to you, no rest till I get to you',
148+
}
149+
150+
for (const title in songs) {
151+
await blobs.set(title, songs[title])
152+
}
153+
154+
const allSongs = await blobs.list()
155+
156+
for (const title in songs) {
157+
const match = allSongs.blobs.find((blob) => blob.key === title)
158+
159+
expect(match).toBeTruthy()
160+
}
161+
162+
const coldplaySongs = await blobs.list({ prefix: 'coldplay/' })
163+
164+
for (const title in songs) {
165+
if (!title.startsWith('coldplay/')) {
166+
continue
167+
}
168+
169+
const match = coldplaySongs.blobs.find((blob) => blob.key === title)
170+
171+
expect(match).toBeTruthy()
172+
}
173+
174+
const parachutesSongs = await blobs.list({ prefix: 'coldplay/parachutes/' })
175+
176+
for (const title in songs) {
177+
if (!title.startsWith('coldplay/parachutes/')) {
178+
continue
179+
}
180+
181+
const match = parachutesSongs.blobs.find((blob) => blob.key === title)
182+
183+
expect(match).toBeTruthy()
184+
}
185+
186+
const fooFightersSongs = await blobs.list({ prefix: 'foo-fighters/' })
187+
188+
expect(fooFightersSongs.blobs).toEqual([])
189+
190+
const artists = await blobs.list({ directories: true })
191+
192+
expect(artists.blobs).toEqual([])
193+
expect(artists.directories).toEqual(['coldplay', 'phoenix'])
194+
195+
const coldplayAlbums = await blobs.list({ directories: true, prefix: 'coldplay/' })
196+
197+
expect(coldplayAlbums.blobs).toEqual([])
198+
expect(coldplayAlbums.directories).toEqual(['coldplay/a-rush-of-blood-to-the-head', 'coldplay/parachutes'])
199+
200+
const parachutesSongs2 = await blobs.list({ directories: true, prefix: 'coldplay/parachutes/' })
201+
202+
for (const title in songs) {
203+
if (!title.startsWith('coldplay/parachutes/')) {
204+
continue
205+
}
206+
207+
const match = parachutesSongs2.blobs.find((blob) => blob.key === title)
208+
209+
expect(match).toBeTruthy()
210+
}
211+
212+
expect(parachutesSongs2.directories).toEqual([])
114213
})

0 commit comments

Comments
 (0)