@flusys/nestjs-storage 6.0.2 → 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/controllers/file-manager.controller.spec.js +135 -0
- package/cjs/controllers/folder.controller.spec.js +86 -0
- package/cjs/controllers/storage-config.controller.spec.js +85 -0
- package/cjs/controllers/upload.controller.spec.js +132 -0
- package/cjs/docs/storage-swagger.config.spec.js +72 -0
- package/cjs/entities/index.spec.js +18 -0
- package/cjs/middlewares/file-serve.middleware.spec.js +211 -0
- package/cjs/modules/storage.module.spec.js +184 -0
- package/cjs/providers/azure-provider.optional.spec.js +228 -0
- package/cjs/providers/local-provider.spec.js +239 -0
- package/cjs/providers/s3-provider.optional.spec.js +243 -0
- package/cjs/providers/sftp-provider.optional.spec.js +217 -0
- package/cjs/providers/storage-factory.service.spec.js +253 -0
- package/cjs/providers/storage-provider.registry.spec.js +67 -0
- package/cjs/services/file-manager.service.spec.js +549 -0
- package/cjs/services/folder.service.spec.js +163 -0
- package/cjs/services/storage-config.service.spec.js +257 -0
- package/cjs/services/storage-datasource.provider.spec.js +217 -0
- package/cjs/services/storage-provider-config.service.spec.js +243 -0
- package/cjs/services/upload.service.spec.js +372 -0
- package/cjs/utils/file-validator.util.spec.js +259 -0
- package/cjs/utils/image-compressor.util.spec.js +166 -0
- package/fesm/controllers/file-manager.controller.spec.js +131 -0
- package/fesm/controllers/folder.controller.spec.js +82 -0
- package/fesm/controllers/storage-config.controller.spec.js +81 -0
- package/fesm/controllers/upload.controller.spec.js +128 -0
- package/fesm/docs/storage-swagger.config.spec.js +68 -0
- package/fesm/entities/index.spec.js +14 -0
- package/fesm/middlewares/file-serve.middleware.spec.js +207 -0
- package/fesm/modules/storage.module.spec.js +180 -0
- package/fesm/providers/azure-provider.optional.spec.js +224 -0
- package/fesm/providers/local-provider.spec.js +194 -0
- package/fesm/providers/s3-provider.optional.spec.js +239 -0
- package/fesm/providers/sftp-provider.optional.spec.js +213 -0
- package/fesm/providers/storage-factory.service.spec.js +249 -0
- package/fesm/providers/storage-provider.registry.spec.js +63 -0
- package/fesm/services/file-manager.service.spec.js +545 -0
- package/fesm/services/folder.service.spec.js +159 -0
- package/fesm/services/storage-config.service.spec.js +253 -0
- package/fesm/services/storage-datasource.provider.spec.js +172 -0
- package/fesm/services/storage-provider-config.service.spec.js +239 -0
- package/fesm/services/upload.service.spec.js +368 -0
- package/fesm/utils/file-validator.util.spec.js +255 -0
- package/fesm/utils/image-compressor.util.spec.js +162 -0
- package/package.json +3 -3
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _testing = require("@nestjs/testing");
|
|
6
|
+
const _modules = require("@flusys/nestjs-shared/modules");
|
|
7
|
+
const _repositorymock = require("@test-utils/mocks/repository.mock");
|
|
8
|
+
const _loggedusermock = require("@test-utils/mocks/logged-user.mock");
|
|
9
|
+
const _folderservice = require("./folder.service");
|
|
10
|
+
const _storageconfigservice = require("./storage-config.service");
|
|
11
|
+
const _storagedatasourceprovider = require("./storage-datasource.provider");
|
|
12
|
+
const _entities = require("../entities");
|
|
13
|
+
describe('FolderService', ()=>{
|
|
14
|
+
let service;
|
|
15
|
+
let mockRepo;
|
|
16
|
+
let mockStorageConfig;
|
|
17
|
+
beforeEach(async ()=>{
|
|
18
|
+
mockRepo = (0, _repositorymock.createMockRepository)();
|
|
19
|
+
const mockDataSourceProvider = (0, _repositorymock.createMockDataSourceProvider)(mockRepo);
|
|
20
|
+
mockStorageConfig = {
|
|
21
|
+
isCompanyFeatureEnabled: jest.fn().mockReturnValue(false)
|
|
22
|
+
};
|
|
23
|
+
const module = await _testing.Test.createTestingModule({
|
|
24
|
+
providers: [
|
|
25
|
+
_folderservice.FolderService,
|
|
26
|
+
{
|
|
27
|
+
provide: 'CACHE_INSTANCE',
|
|
28
|
+
useValue: {}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
provide: _modules.UtilsService,
|
|
32
|
+
useValue: {}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
provide: _storageconfigservice.StorageConfigService,
|
|
36
|
+
useValue: mockStorageConfig
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
provide: _storagedatasourceprovider.StorageDataSourceProvider,
|
|
40
|
+
useValue: mockDataSourceProvider
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}).compile();
|
|
44
|
+
service = await module.resolve(_folderservice.FolderService);
|
|
45
|
+
});
|
|
46
|
+
describe('resolveEntity', ()=>{
|
|
47
|
+
it('resolves to the core Folder entity when company feature is disabled', ()=>{
|
|
48
|
+
expect(service.resolveEntity()).toBe(_entities.Folder);
|
|
49
|
+
});
|
|
50
|
+
it('resolves to FolderWithCompany when company feature is enabled', ()=>{
|
|
51
|
+
mockStorageConfig.isCompanyFeatureEnabled.mockReturnValue(true);
|
|
52
|
+
expect(service.resolveEntity()).toBe(_entities.FolderWithCompany);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('convertSingleDtoToEntity', ()=>{
|
|
56
|
+
beforeEach(async ()=>{
|
|
57
|
+
mockRepo.create.mockReturnValue({});
|
|
58
|
+
await service.ensureDataSourceRepository();
|
|
59
|
+
});
|
|
60
|
+
it('does not attach companyId when the company feature is disabled', async ()=>{
|
|
61
|
+
const entity = await service.convertSingleDtoToEntity({
|
|
62
|
+
name: 'Invoices'
|
|
63
|
+
}, (0, _loggedusermock.buildMockUser)());
|
|
64
|
+
expect(entity.companyId).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
it('attaches the current user companyId when the company feature is enabled', async ()=>{
|
|
67
|
+
mockStorageConfig.isCompanyFeatureEnabled.mockReturnValue(true);
|
|
68
|
+
const entity = await service.convertSingleDtoToEntity({
|
|
69
|
+
name: 'Invoices'
|
|
70
|
+
}, (0, _loggedusermock.buildMockUser)({
|
|
71
|
+
companyId: 'company-7'
|
|
72
|
+
}));
|
|
73
|
+
expect(entity.companyId).toBe('company-7');
|
|
74
|
+
});
|
|
75
|
+
it('defaults companyId to null when company feature is enabled but user has none', async ()=>{
|
|
76
|
+
mockStorageConfig.isCompanyFeatureEnabled.mockReturnValue(true);
|
|
77
|
+
const entity = await service.convertSingleDtoToEntity({
|
|
78
|
+
name: 'Invoices'
|
|
79
|
+
}, null);
|
|
80
|
+
expect(entity.companyId).toBeNull();
|
|
81
|
+
});
|
|
82
|
+
it('loads and merges the existing entity when updating (dto has an id)', async ()=>{
|
|
83
|
+
const existing = {
|
|
84
|
+
id: 'folder-1',
|
|
85
|
+
name: 'Old Name'
|
|
86
|
+
};
|
|
87
|
+
mockRepo.findOne.mockResolvedValue(existing);
|
|
88
|
+
const entity = await service.convertSingleDtoToEntity({
|
|
89
|
+
id: 'folder-1',
|
|
90
|
+
name: 'New Name'
|
|
91
|
+
}, (0, _loggedusermock.buildMockUser)());
|
|
92
|
+
expect(mockRepo.findOne).toHaveBeenCalledWith({
|
|
93
|
+
where: {
|
|
94
|
+
id: 'folder-1'
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
expect(entity.name).toBe('New Name');
|
|
98
|
+
});
|
|
99
|
+
it('throws NotFoundException when updating a folder that does not exist', async ()=>{
|
|
100
|
+
mockRepo.findOne.mockResolvedValue(null);
|
|
101
|
+
await expect(service.convertSingleDtoToEntity({
|
|
102
|
+
id: 'missing-id',
|
|
103
|
+
name: 'x'
|
|
104
|
+
}, (0, _loggedusermock.buildMockUser)())).rejects.toThrow();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
describe('getSelectQuery', ()=>{
|
|
108
|
+
it('selects the default field set (id, name, slug, createdAt, deletedAt) when none is provided', async ()=>{
|
|
109
|
+
const query = {
|
|
110
|
+
select: jest.fn().mockReturnThis()
|
|
111
|
+
};
|
|
112
|
+
await service.getSelectQuery(query, (0, _loggedusermock.buildMockUser)());
|
|
113
|
+
expect(query.select).toHaveBeenCalledWith([
|
|
114
|
+
'folder.id',
|
|
115
|
+
'folder.name',
|
|
116
|
+
'folder.slug',
|
|
117
|
+
'folder.createdAt',
|
|
118
|
+
'folder.deletedAt'
|
|
119
|
+
]);
|
|
120
|
+
});
|
|
121
|
+
it('adds companyId to the default select list when company feature is enabled', async ()=>{
|
|
122
|
+
mockStorageConfig.isCompanyFeatureEnabled.mockReturnValue(true);
|
|
123
|
+
const query = {
|
|
124
|
+
select: jest.fn().mockReturnThis()
|
|
125
|
+
};
|
|
126
|
+
await service.getSelectQuery(query, (0, _loggedusermock.buildMockUser)());
|
|
127
|
+
const selected = query.select.mock.calls[0][0];
|
|
128
|
+
expect(selected).toContain('folder.companyId');
|
|
129
|
+
});
|
|
130
|
+
it('respects an explicitly provided select list', async ()=>{
|
|
131
|
+
const query = {
|
|
132
|
+
select: jest.fn().mockReturnThis()
|
|
133
|
+
};
|
|
134
|
+
await service.getSelectQuery(query, (0, _loggedusermock.buildMockUser)(), [
|
|
135
|
+
'id'
|
|
136
|
+
]);
|
|
137
|
+
expect(query.select).toHaveBeenCalledWith([
|
|
138
|
+
'folder.id'
|
|
139
|
+
]);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
describe('getExtraManipulateQuery', ()=>{
|
|
143
|
+
it('applies the company filter when company feature is enabled', async ()=>{
|
|
144
|
+
mockStorageConfig.isCompanyFeatureEnabled.mockReturnValue(true);
|
|
145
|
+
const query = {
|
|
146
|
+
andWhere: jest.fn().mockReturnThis()
|
|
147
|
+
};
|
|
148
|
+
await service.getExtraManipulateQuery(query, {}, (0, _loggedusermock.buildMockUser)({
|
|
149
|
+
companyId: 'company-3'
|
|
150
|
+
}));
|
|
151
|
+
expect(query.andWhere).toHaveBeenCalledWith('folder.companyId = :companyId', {
|
|
152
|
+
companyId: 'company-3'
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
it('does not apply a company filter when company feature is disabled', async ()=>{
|
|
156
|
+
const query = {
|
|
157
|
+
andWhere: jest.fn().mockReturnThis()
|
|
158
|
+
};
|
|
159
|
+
await service.getExtraManipulateQuery(query, {}, (0, _loggedusermock.buildMockUser)());
|
|
160
|
+
expect(query.andWhere).not.toHaveBeenCalled();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _storageconfigservice = require("./storage-config.service");
|
|
6
|
+
const _config = require("../config");
|
|
7
|
+
function buildOptions(overrides = {}) {
|
|
8
|
+
return {
|
|
9
|
+
bootstrapAppConfig: {
|
|
10
|
+
databaseMode: 'single',
|
|
11
|
+
enableCompanyFeature: false
|
|
12
|
+
},
|
|
13
|
+
config: {},
|
|
14
|
+
...overrides
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
describe('StorageConfigService', ()=>{
|
|
18
|
+
describe('bootstrap config getters', ()=>{
|
|
19
|
+
it('reflects enableCompanyFeature from bootstrap config', ()=>{
|
|
20
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
21
|
+
bootstrapAppConfig: {
|
|
22
|
+
databaseMode: 'single',
|
|
23
|
+
enableCompanyFeature: true
|
|
24
|
+
}
|
|
25
|
+
}));
|
|
26
|
+
expect(service.isCompanyFeatureEnabled()).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
it('defaults enableCompanyFeature to false when bootstrapAppConfig is absent', ()=>{
|
|
29
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
30
|
+
bootstrapAppConfig: undefined
|
|
31
|
+
}));
|
|
32
|
+
expect(service.isCompanyFeatureEnabled()).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
it('defaults databaseMode to single when bootstrapAppConfig is absent', ()=>{
|
|
35
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
36
|
+
bootstrapAppConfig: undefined
|
|
37
|
+
}));
|
|
38
|
+
expect(service.getDatabaseMode()).toBe('single');
|
|
39
|
+
});
|
|
40
|
+
it('reports isMultiTenant() true only in multi-tenant mode', ()=>{
|
|
41
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
42
|
+
bootstrapAppConfig: {
|
|
43
|
+
databaseMode: 'multi-tenant',
|
|
44
|
+
enableCompanyFeature: true
|
|
45
|
+
}
|
|
46
|
+
}));
|
|
47
|
+
expect(service.isMultiTenant()).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
it('reports isMultiTenant() false in single mode', ()=>{
|
|
50
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions());
|
|
51
|
+
expect(service.isMultiTenant()).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe('getMaxFileSize', ()=>{
|
|
55
|
+
it('returns the configured max file size', ()=>{
|
|
56
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
57
|
+
config: {
|
|
58
|
+
maxFileSize: 5000
|
|
59
|
+
}
|
|
60
|
+
}));
|
|
61
|
+
expect(service.getMaxFileSize()).toBe(5000);
|
|
62
|
+
});
|
|
63
|
+
it('falls back to the default max file size (100MB) when not configured', ()=>{
|
|
64
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
65
|
+
config: {}
|
|
66
|
+
}));
|
|
67
|
+
expect(service.getMaxFileSize()).toBe(100 * 1024 * 1024);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('getAllowedFileTypes', ()=>{
|
|
71
|
+
it('returns configured allowed types', ()=>{
|
|
72
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
73
|
+
config: {
|
|
74
|
+
allowedFileTypes: [
|
|
75
|
+
'image/png'
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
}));
|
|
79
|
+
expect(service.getAllowedFileTypes()).toEqual([
|
|
80
|
+
'image/png'
|
|
81
|
+
]);
|
|
82
|
+
});
|
|
83
|
+
it('defaults to */* when not configured', ()=>{
|
|
84
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
85
|
+
config: {}
|
|
86
|
+
}));
|
|
87
|
+
expect(service.getAllowedFileTypes()).toEqual([
|
|
88
|
+
'*/*'
|
|
89
|
+
]);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe('getDefaultLocalStoragePath', ()=>{
|
|
93
|
+
it('returns the configured local storage path', ()=>{
|
|
94
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
95
|
+
config: {
|
|
96
|
+
localStoragePath: './custom-uploads'
|
|
97
|
+
}
|
|
98
|
+
}));
|
|
99
|
+
expect(service.getDefaultLocalStoragePath()).toBe('./custom-uploads');
|
|
100
|
+
});
|
|
101
|
+
it('defaults to ./uploads when not configured', ()=>{
|
|
102
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
103
|
+
config: {}
|
|
104
|
+
}));
|
|
105
|
+
expect(service.getDefaultLocalStoragePath()).toBe('./uploads');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
describe('getAppUrl', ()=>{
|
|
109
|
+
const originalAppUrl = process.env.APP_URL;
|
|
110
|
+
const originalPort = process.env.PORT;
|
|
111
|
+
afterEach(()=>{
|
|
112
|
+
process.env.APP_URL = originalAppUrl;
|
|
113
|
+
process.env.PORT = originalPort;
|
|
114
|
+
});
|
|
115
|
+
it('returns the explicitly configured appUrl', ()=>{
|
|
116
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
117
|
+
config: {
|
|
118
|
+
appUrl: 'https://files.example.com'
|
|
119
|
+
}
|
|
120
|
+
}));
|
|
121
|
+
expect(service.getAppUrl()).toBe('https://files.example.com');
|
|
122
|
+
});
|
|
123
|
+
it('falls back to process.env.APP_URL when config.appUrl is absent', ()=>{
|
|
124
|
+
delete process.env.APP_URL;
|
|
125
|
+
process.env.APP_URL = 'https://env.example.com';
|
|
126
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
127
|
+
config: {}
|
|
128
|
+
}));
|
|
129
|
+
expect(service.getAppUrl()).toBe('https://env.example.com');
|
|
130
|
+
});
|
|
131
|
+
it('falls back to localhost with PORT when neither config nor env var is set', ()=>{
|
|
132
|
+
delete process.env.APP_URL;
|
|
133
|
+
process.env.PORT = '4000';
|
|
134
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
135
|
+
config: {}
|
|
136
|
+
}));
|
|
137
|
+
expect(service.getAppUrl()).toBe('http://localhost:4000');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
describe('isFileTypeAllowed', ()=>{
|
|
141
|
+
it('allows any type when */* is configured', ()=>{
|
|
142
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
143
|
+
config: {
|
|
144
|
+
allowedFileTypes: [
|
|
145
|
+
'*/*'
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
}));
|
|
149
|
+
expect(service.isFileTypeAllowed('application/pdf')).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
it('allows types matching a category wildcard', ()=>{
|
|
152
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
153
|
+
config: {
|
|
154
|
+
allowedFileTypes: [
|
|
155
|
+
'image/*'
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
}));
|
|
159
|
+
expect(service.isFileTypeAllowed('image/png')).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
it('rejects types outside the allow-list', ()=>{
|
|
162
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
163
|
+
config: {
|
|
164
|
+
allowedFileTypes: [
|
|
165
|
+
'image/*'
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
}));
|
|
169
|
+
expect(service.isFileTypeAllowed('application/pdf')).toBe(false);
|
|
170
|
+
});
|
|
171
|
+
it('allows an exact MIME type match', ()=>{
|
|
172
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
173
|
+
config: {
|
|
174
|
+
allowedFileTypes: [
|
|
175
|
+
'application/pdf'
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
}));
|
|
179
|
+
expect(service.isFileTypeAllowed('application/pdf')).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
describe('validateFileSize', ()=>{
|
|
183
|
+
it('passes when file size is within the limit', ()=>{
|
|
184
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
185
|
+
config: {
|
|
186
|
+
maxFileSize: 1000
|
|
187
|
+
}
|
|
188
|
+
}));
|
|
189
|
+
expect(service.validateFileSize(500)).toEqual({
|
|
190
|
+
valid: true
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
it('passes at the exact boundary size', ()=>{
|
|
194
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
195
|
+
config: {
|
|
196
|
+
maxFileSize: 1000
|
|
197
|
+
}
|
|
198
|
+
}));
|
|
199
|
+
expect(service.validateFileSize(1000).valid).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
it('fails when file size exceeds the limit by one byte', ()=>{
|
|
202
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
203
|
+
config: {
|
|
204
|
+
maxFileSize: 1000
|
|
205
|
+
}
|
|
206
|
+
}));
|
|
207
|
+
const result = service.validateFileSize(1001);
|
|
208
|
+
expect(result.valid).toBe(false);
|
|
209
|
+
expect(result.messageKey).toBe(_config.UPLOAD_MESSAGES.FILE_TOO_LARGE);
|
|
210
|
+
expect(result.messageVariables).toEqual({
|
|
211
|
+
fileSize: '0.00',
|
|
212
|
+
maxSize: '0.00'
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
it('reports MB-converted size values in the failure message', ()=>{
|
|
216
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
217
|
+
config: {
|
|
218
|
+
maxFileSize: 1 * 1024 * 1024
|
|
219
|
+
}
|
|
220
|
+
}));
|
|
221
|
+
const result = service.validateFileSize(2 * 1024 * 1024);
|
|
222
|
+
expect(result.message).toContain('2.00MB');
|
|
223
|
+
expect(result.message).toContain('1.00MB');
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
describe('validateFileType', ()=>{
|
|
227
|
+
it('passes for an allowed type', ()=>{
|
|
228
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
229
|
+
config: {
|
|
230
|
+
allowedFileTypes: [
|
|
231
|
+
'image/*'
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
}));
|
|
235
|
+
expect(service.validateFileType('image/png')).toEqual({
|
|
236
|
+
valid: true
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
it('fails for a disallowed type with a descriptive message and messageKey', ()=>{
|
|
240
|
+
const service = new _storageconfigservice.StorageConfigService(buildOptions({
|
|
241
|
+
config: {
|
|
242
|
+
allowedFileTypes: [
|
|
243
|
+
'image/*'
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
}));
|
|
247
|
+
const result = service.validateFileType('application/pdf');
|
|
248
|
+
expect(result.valid).toBe(false);
|
|
249
|
+
expect(result.messageKey).toBe(_config.UPLOAD_MESSAGES.INVALID_TYPE);
|
|
250
|
+
expect(result.message).toContain('application/pdf');
|
|
251
|
+
expect(result.messageVariables).toEqual({
|
|
252
|
+
mimeType: 'application/pdf',
|
|
253
|
+
allowedTypes: 'image/*'
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
});
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _modules = require("@flusys/nestjs-shared/modules");
|
|
6
|
+
const _storagedatasourceprovider = require("./storage-datasource.provider");
|
|
7
|
+
const _entities = /*#__PURE__*/ _interop_require_wildcard(require("../entities"));
|
|
8
|
+
function _getRequireWildcardCache(nodeInterop) {
|
|
9
|
+
if (typeof WeakMap !== "function") return null;
|
|
10
|
+
var cacheBabelInterop = new WeakMap();
|
|
11
|
+
var cacheNodeInterop = new WeakMap();
|
|
12
|
+
return (_getRequireWildcardCache = function(nodeInterop) {
|
|
13
|
+
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
|
|
14
|
+
})(nodeInterop);
|
|
15
|
+
}
|
|
16
|
+
function _interop_require_wildcard(obj, nodeInterop) {
|
|
17
|
+
if (!nodeInterop && obj && obj.__esModule) {
|
|
18
|
+
return obj;
|
|
19
|
+
}
|
|
20
|
+
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
|
|
21
|
+
return {
|
|
22
|
+
default: obj
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
var cache = _getRequireWildcardCache(nodeInterop);
|
|
26
|
+
if (cache && cache.has(obj)) {
|
|
27
|
+
return cache.get(obj);
|
|
28
|
+
}
|
|
29
|
+
var newObj = {
|
|
30
|
+
__proto__: null
|
|
31
|
+
};
|
|
32
|
+
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
33
|
+
for(var key in obj){
|
|
34
|
+
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
35
|
+
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
|
|
36
|
+
if (desc && (desc.get || desc.set)) {
|
|
37
|
+
Object.defineProperty(newObj, key, desc);
|
|
38
|
+
} else {
|
|
39
|
+
newObj[key] = obj[key];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
newObj.default = obj;
|
|
44
|
+
if (cache) {
|
|
45
|
+
cache.set(obj, newObj);
|
|
46
|
+
}
|
|
47
|
+
return newObj;
|
|
48
|
+
}
|
|
49
|
+
function buildMockConfigService(overrides = {}) {
|
|
50
|
+
const options = {
|
|
51
|
+
bootstrapAppConfig: {
|
|
52
|
+
databaseMode: 'single',
|
|
53
|
+
enableCompanyFeature: false
|
|
54
|
+
},
|
|
55
|
+
config: {},
|
|
56
|
+
...overrides
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
isCompanyFeatureEnabled: jest.fn().mockReturnValue(options.bootstrapAppConfig?.enableCompanyFeature ?? false),
|
|
60
|
+
getOptions: jest.fn().mockReturnValue(options)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
describe('StorageDataSourceProvider', ()=>{
|
|
64
|
+
afterEach(()=>{
|
|
65
|
+
jest.restoreAllMocks();
|
|
66
|
+
});
|
|
67
|
+
describe('construction', ()=>{
|
|
68
|
+
it('propagates bootstrap options from StorageConfigService into the parent data-source service', ()=>{
|
|
69
|
+
const configService = buildMockConfigService({
|
|
70
|
+
bootstrapAppConfig: {
|
|
71
|
+
databaseMode: 'multi-tenant',
|
|
72
|
+
enableCompanyFeature: true
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
const provider = new _storagedatasourceprovider.StorageDataSourceProvider(configService);
|
|
76
|
+
expect(provider.getDatabaseMode()).toBe('multi-tenant');
|
|
77
|
+
expect(provider.isMultiTenant()).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
it('defaults to single database mode when bootstrap config is absent', ()=>{
|
|
80
|
+
const configService = buildMockConfigService({
|
|
81
|
+
bootstrapAppConfig: undefined
|
|
82
|
+
});
|
|
83
|
+
const provider = new _storagedatasourceprovider.StorageDataSourceProvider(configService);
|
|
84
|
+
expect(provider.getDatabaseMode()).toBe('single');
|
|
85
|
+
expect(provider.isMultiTenant()).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
describe('getEnableCompanyFeatureForTenant', ()=>{
|
|
89
|
+
it('uses the tenant-level override when provided', ()=>{
|
|
90
|
+
const configService = buildMockConfigService();
|
|
91
|
+
const provider = new _storagedatasourceprovider.StorageDataSourceProvider(configService);
|
|
92
|
+
const result = provider.getEnableCompanyFeatureForTenant({
|
|
93
|
+
id: 'tenant-1',
|
|
94
|
+
enableCompanyFeature: true
|
|
95
|
+
});
|
|
96
|
+
expect(result).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
it('falls back to global config when tenant has no override', ()=>{
|
|
99
|
+
const configService = buildMockConfigService({
|
|
100
|
+
bootstrapAppConfig: {
|
|
101
|
+
databaseMode: 'single',
|
|
102
|
+
enableCompanyFeature: true
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
const provider = new _storagedatasourceprovider.StorageDataSourceProvider(configService);
|
|
106
|
+
const result = provider.getEnableCompanyFeatureForTenant({
|
|
107
|
+
id: 'tenant-1'
|
|
108
|
+
});
|
|
109
|
+
expect(result).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
it('falls back to global config when no tenant is provided', ()=>{
|
|
112
|
+
const configService = buildMockConfigService({
|
|
113
|
+
bootstrapAppConfig: {
|
|
114
|
+
databaseMode: 'single',
|
|
115
|
+
enableCompanyFeature: false
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
const provider = new _storagedatasourceprovider.StorageDataSourceProvider(configService);
|
|
119
|
+
expect(provider.getEnableCompanyFeatureForTenant(undefined)).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
describe('getStorageEntities', ()=>{
|
|
123
|
+
it('returns company-scoped entities when company feature is enabled', async ()=>{
|
|
124
|
+
const configService = buildMockConfigService();
|
|
125
|
+
const provider = new _storagedatasourceprovider.StorageDataSourceProvider(configService);
|
|
126
|
+
const result = await provider.getStorageEntities(true);
|
|
127
|
+
expect(result).toEqual([
|
|
128
|
+
_entities.FileManagerWithCompany,
|
|
129
|
+
_entities.FolderWithCompany,
|
|
130
|
+
_entities.StorageConfigWithCompany
|
|
131
|
+
]);
|
|
132
|
+
});
|
|
133
|
+
it('returns core entities when company feature is disabled', async ()=>{
|
|
134
|
+
const configService = buildMockConfigService();
|
|
135
|
+
const provider = new _storagedatasourceprovider.StorageDataSourceProvider(configService);
|
|
136
|
+
const result = await provider.getStorageEntities(false);
|
|
137
|
+
expect(result).toEqual([
|
|
138
|
+
_entities.FileManager,
|
|
139
|
+
_entities.Folder,
|
|
140
|
+
_entities.StorageConfig
|
|
141
|
+
]);
|
|
142
|
+
});
|
|
143
|
+
it('falls back to the config service value when no explicit flag is passed', async ()=>{
|
|
144
|
+
const configService = buildMockConfigService({
|
|
145
|
+
bootstrapAppConfig: {
|
|
146
|
+
databaseMode: 'single',
|
|
147
|
+
enableCompanyFeature: true
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
const provider = new _storagedatasourceprovider.StorageDataSourceProvider(configService);
|
|
151
|
+
const result = await provider.getStorageEntities();
|
|
152
|
+
expect(result).toEqual([
|
|
153
|
+
_entities.FileManagerWithCompany,
|
|
154
|
+
_entities.FolderWithCompany,
|
|
155
|
+
_entities.StorageConfigWithCompany
|
|
156
|
+
]);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
describe('createDataSourceFromConfig', ()=>{
|
|
160
|
+
it('resolves storage entities for the current tenant/config and delegates to the parent implementation', async ()=>{
|
|
161
|
+
const configService = buildMockConfigService({
|
|
162
|
+
bootstrapAppConfig: {
|
|
163
|
+
databaseMode: 'single',
|
|
164
|
+
enableCompanyFeature: false
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
const provider = new _storagedatasourceprovider.StorageDataSourceProvider(configService);
|
|
168
|
+
jest.spyOn(provider, 'getCurrentTenant').mockReturnValue(null);
|
|
169
|
+
const superCreateSpy = jest.spyOn(_modules.MultiTenantDataSourceService.prototype, 'createDataSourceFromConfig').mockResolvedValue({
|
|
170
|
+
isInitialized: true
|
|
171
|
+
});
|
|
172
|
+
const config = {
|
|
173
|
+
type: 'mysql',
|
|
174
|
+
host: 'localhost',
|
|
175
|
+
port: 3306,
|
|
176
|
+
username: 'root',
|
|
177
|
+
password: '',
|
|
178
|
+
database: 'test'
|
|
179
|
+
};
|
|
180
|
+
const result = await provider.createDataSourceFromConfig(config);
|
|
181
|
+
expect(superCreateSpy).toHaveBeenCalledWith(config, [
|
|
182
|
+
_entities.FileManager,
|
|
183
|
+
_entities.Folder,
|
|
184
|
+
_entities.StorageConfig
|
|
185
|
+
]);
|
|
186
|
+
expect(result).toEqual({
|
|
187
|
+
isInitialized: true
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
it('uses the tenant enableCompanyFeature override to pick company-scoped entities', async ()=>{
|
|
191
|
+
const configService = buildMockConfigService({
|
|
192
|
+
bootstrapAppConfig: {
|
|
193
|
+
databaseMode: 'multi-tenant',
|
|
194
|
+
enableCompanyFeature: false
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
const provider = new _storagedatasourceprovider.StorageDataSourceProvider(configService);
|
|
198
|
+
jest.spyOn(provider, 'getCurrentTenant').mockReturnValue({
|
|
199
|
+
id: 'tenant-1',
|
|
200
|
+
enableCompanyFeature: true
|
|
201
|
+
});
|
|
202
|
+
const superCreateSpy = jest.spyOn(_modules.MultiTenantDataSourceService.prototype, 'createDataSourceFromConfig').mockResolvedValue({
|
|
203
|
+
isInitialized: true
|
|
204
|
+
});
|
|
205
|
+
const config = {
|
|
206
|
+
type: 'mysql',
|
|
207
|
+
database: 'tenant_db'
|
|
208
|
+
};
|
|
209
|
+
await provider.createDataSourceFromConfig(config);
|
|
210
|
+
expect(superCreateSpy).toHaveBeenCalledWith(config, [
|
|
211
|
+
_entities.FileManagerWithCompany,
|
|
212
|
+
_entities.FolderWithCompany,
|
|
213
|
+
_entities.StorageConfigWithCompany
|
|
214
|
+
]);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
});
|