Skip to content

Commit 1b6889f

Browse files
authored
@tus/file-store: fix list method of FileConfigstore (#490)
1 parent 9d14d7d commit 1b6889f

File tree

3 files changed

+60
-11
lines changed

3 files changed

+60
-11
lines changed

packages/file-store/configstores/FileConfigstore.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@ export class FileConfigstore implements Configstore {
3737

3838
async list(): Promise<Array<string>> {
3939
return this.queue.add(async () => {
40-
const files = await fs.readdir(this.directory, {withFileTypes: true})
41-
const promises = files
42-
.filter((file) => file.isFile() && file.name.endsWith('.json'))
43-
.map((file) => fs.readFile(path.resolve(file.path, file.name), 'utf8'))
44-
45-
return Promise.all(promises)
46-
}) as Promise<string[]>
40+
const files = await fs.readdir(this.directory)
41+
const sorted = files.sort((a, b) => a.localeCompare(b))
42+
const name = (file: string) => path.basename(file, '.json')
43+
// To only return tus file IDs we check if the file has a corresponding JSON info file
44+
return sorted.filter(
45+
(file, idx) => idx < sorted.length - 1 && name(file) === name(sorted[idx + 1])
46+
)
47+
})
4748
}
4849

4950
private resolve(key: string): string {

packages/file-store/test.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,24 @@ import 'should'
22

33
import {strict as assert} from 'node:assert'
44
import fs from 'node:fs'
5+
import fsProm from 'node:fs/promises'
56
import path from 'node:path'
67

78
import sinon from 'sinon'
89

9-
import {FileStore} from './'
10+
import {FileStore, FileConfigstore} from './'
1011
import {Upload} from '@tus/server'
1112

1213
import * as shared from '../../test/stores.test'
13-
import {MemoryConfigstore} from './configstores/MemoryConfigstore'
1414

1515
const fixturesPath = path.resolve('../', '../', 'test', 'fixtures')
1616
const storePath = path.resolve('../', '../', 'test', 'output')
1717

18+
async function cleanup() {
19+
await fsProm.rm(storePath, {recursive: true})
20+
await fsProm.mkdir(storePath)
21+
}
22+
1823
describe('FileStore', function () {
1924
before(function () {
2025
this.testFileSize = 960_244
@@ -28,13 +33,13 @@ describe('FileStore', function () {
2833
sinon.spy(fs, 'mkdir')
2934
this.datastore = new FileStore({
3035
directory: this.storePath,
31-
configstore: new MemoryConfigstore(),
3236
})
3337
})
3438

35-
this.afterEach(() => {
39+
this.afterEach(async () => {
3640
// @ts-expect-error ignore
3741
fs.mkdir.restore()
42+
await cleanup()
3843
})
3944

4045
it('should create a directory for the files', function (done) {
@@ -89,9 +94,26 @@ describe('FileStore', function () {
8994
})
9095
})
9196

97+
describe('FileConfigstore', () => {
98+
it('should ignore random files in directory when calling list()', async function () {
99+
const store = new FileConfigstore(storePath)
100+
const files = ['tus', 'tus.json', 'tu', 'tuss.json', 'random']
101+
for (const file of files) {
102+
await fsProm.writeFile(path.resolve(storePath, file), '')
103+
}
104+
const list = await store.list()
105+
106+
// list returns the amount of uploads.
107+
// One upload consists of the file and the JSON info file.
108+
// But from the list perspective that is only one upload.
109+
assert.strictEqual(list.length, 1)
110+
})
111+
})
112+
92113
shared.shouldHaveStoreMethods()
93114
shared.shouldCreateUploads()
94115
shared.shouldRemoveUploads() // Termination extension
116+
shared.shouldExpireUploads() // Expiration extension
95117
shared.shouldWriteUploads()
96118
shared.shouldHandleOffset()
97119
shared.shouldDeclareUploadLength() // Creation-defer-length extension

test/stores.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'should'
22
import {strict as assert} from 'node:assert'
33
import fs from 'node:fs'
44
import stream from 'node:stream'
5+
import {setTimeout as promSetTimeout} from 'node:timers/promises'
56

67
import {Upload} from '@tus/server'
78

@@ -67,6 +68,31 @@ export const shouldCreateUploads = function () {
6768
})
6869
}
6970

71+
export const shouldExpireUploads = function () {
72+
describe('expiration extension', () => {
73+
it("should report 'expiration' extension", function () {
74+
assert.equal(this.datastore.hasExtension('expiration'), true)
75+
})
76+
77+
it('should expire upload', async function () {
78+
const file = new Upload({
79+
id: 'expiration-test',
80+
size: this.testFileSize,
81+
offset: 0,
82+
metadata: {filename: 'world_domination_plan.pdf', is_confidential: null},
83+
})
84+
this.datastore.expirationPeriodInMilliseconds = 100
85+
await this.datastore.create(file)
86+
const readable = fs.createReadStream(this.testFilePath)
87+
const offset = await this.datastore.write(readable, file.id, 0)
88+
await promSetTimeout(100)
89+
const n = await this.datastore.deleteExpired()
90+
assert.equal(offset, this.testFileSize)
91+
assert.equal(n, 1)
92+
})
93+
})
94+
}
95+
7096
export const shouldRemoveUploads = function () {
7197
const file = new Upload({id: 'remove-test', size: 1000, offset: 0})
7298

0 commit comments

Comments
 (0)