-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(server): move timeline operations to their own controller/se…
…rvice (#8325) * move timeline operations to their own controller/service * chore: open api * move e2e tests
- Loading branch information
1 parent
b8b3c48
commit b8c5363
Showing
20 changed files
with
1,326 additions
and
1,156 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
import { AssetFileUploadResponseDto, LoginResponseDto, SharedLinkType, TimeBucketSize } from '@immich/sdk'; | ||
import { DateTime } from 'luxon'; | ||
import { createUserDto } from 'src/fixtures'; | ||
import { errorDto } from 'src/responses'; | ||
import { app, utils } from 'src/utils'; | ||
import request from 'supertest'; | ||
import { beforeAll, describe, expect, it } from 'vitest'; | ||
|
||
// TODO this should probably be a test util function | ||
const today = DateTime.fromObject({ | ||
year: 2023, | ||
month: 11, | ||
day: 3, | ||
}) as DateTime<true>; | ||
const yesterday = today.minus({ days: 1 }); | ||
|
||
describe('/timeline', () => { | ||
let admin: LoginResponseDto; | ||
let user: LoginResponseDto; | ||
let timeBucketUser: LoginResponseDto; | ||
|
||
let userAssets: AssetFileUploadResponseDto[]; | ||
|
||
beforeAll(async () => { | ||
await utils.resetDatabase(); | ||
admin = await utils.adminSetup({ onboarding: false }); | ||
[user, timeBucketUser] = await Promise.all([ | ||
utils.userSetup(admin.accessToken, createUserDto.create('1')), | ||
utils.userSetup(admin.accessToken, createUserDto.create('time-bucket')), | ||
]); | ||
|
||
userAssets = await Promise.all([ | ||
utils.createAsset(user.accessToken), | ||
utils.createAsset(user.accessToken), | ||
utils.createAsset(user.accessToken, { | ||
isFavorite: true, | ||
isReadOnly: true, | ||
fileCreatedAt: yesterday.toISO(), | ||
fileModifiedAt: yesterday.toISO(), | ||
assetData: { filename: 'example.mp4' }, | ||
}), | ||
utils.createAsset(user.accessToken), | ||
utils.createAsset(user.accessToken), | ||
]); | ||
|
||
await Promise.all([ | ||
utils.createAsset(timeBucketUser.accessToken, { fileCreatedAt: new Date('1970-01-01').toISOString() }), | ||
utils.createAsset(timeBucketUser.accessToken, { fileCreatedAt: new Date('1970-02-10').toISOString() }), | ||
utils.createAsset(timeBucketUser.accessToken, { fileCreatedAt: new Date('1970-02-11').toISOString() }), | ||
utils.createAsset(timeBucketUser.accessToken, { fileCreatedAt: new Date('1970-02-11').toISOString() }), | ||
]); | ||
}); | ||
|
||
describe('GET /timeline/buckets', () => { | ||
it('should require authentication', async () => { | ||
const { status, body } = await request(app).get('/timeline/buckets').query({ size: TimeBucketSize.Month }); | ||
expect(status).toBe(401); | ||
expect(body).toEqual(errorDto.unauthorized); | ||
}); | ||
|
||
it('should get time buckets by month', async () => { | ||
const { status, body } = await request(app) | ||
.get('/timeline/buckets') | ||
.set('Authorization', `Bearer ${timeBucketUser.accessToken}`) | ||
.query({ size: TimeBucketSize.Month }); | ||
|
||
expect(status).toBe(200); | ||
expect(body).toEqual( | ||
expect.arrayContaining([ | ||
{ count: 3, timeBucket: '1970-02-01T00:00:00.000Z' }, | ||
{ count: 1, timeBucket: '1970-01-01T00:00:00.000Z' }, | ||
]), | ||
); | ||
}); | ||
|
||
it('should not allow access for unrelated shared links', async () => { | ||
const sharedLink = await utils.createSharedLink(user.accessToken, { | ||
type: SharedLinkType.Individual, | ||
assetIds: userAssets.map(({ id }) => id), | ||
}); | ||
|
||
const { status, body } = await request(app) | ||
.get('/timeline/buckets') | ||
.query({ key: sharedLink.key, size: TimeBucketSize.Month }); | ||
|
||
expect(status).toBe(400); | ||
expect(body).toEqual(errorDto.noPermission); | ||
}); | ||
|
||
it('should get time buckets by day', async () => { | ||
const { status, body } = await request(app) | ||
.get('/timeline/buckets') | ||
.set('Authorization', `Bearer ${timeBucketUser.accessToken}`) | ||
.query({ size: TimeBucketSize.Day }); | ||
|
||
expect(status).toBe(200); | ||
expect(body).toEqual([ | ||
{ count: 2, timeBucket: '1970-02-11T00:00:00.000Z' }, | ||
{ count: 1, timeBucket: '1970-02-10T00:00:00.000Z' }, | ||
{ count: 1, timeBucket: '1970-01-01T00:00:00.000Z' }, | ||
]); | ||
}); | ||
|
||
it('should return error if time bucket is requested with partners asset and archived', async () => { | ||
const req1 = await request(app) | ||
.get('/timeline/buckets') | ||
.set('Authorization', `Bearer ${timeBucketUser.accessToken}`) | ||
.query({ size: TimeBucketSize.Month, withPartners: true, isArchived: true }); | ||
|
||
expect(req1.status).toBe(400); | ||
expect(req1.body).toEqual(errorDto.badRequest()); | ||
|
||
const req2 = await request(app) | ||
.get('/timeline/buckets') | ||
.set('Authorization', `Bearer ${user.accessToken}`) | ||
.query({ size: TimeBucketSize.Month, withPartners: true, isArchived: undefined }); | ||
|
||
expect(req2.status).toBe(400); | ||
expect(req2.body).toEqual(errorDto.badRequest()); | ||
}); | ||
|
||
it('should return error if time bucket is requested with partners asset and favorite', async () => { | ||
const req1 = await request(app) | ||
.get('/timeline/buckets') | ||
.set('Authorization', `Bearer ${timeBucketUser.accessToken}`) | ||
.query({ size: TimeBucketSize.Month, withPartners: true, isFavorite: true }); | ||
|
||
expect(req1.status).toBe(400); | ||
expect(req1.body).toEqual(errorDto.badRequest()); | ||
|
||
const req2 = await request(app) | ||
.get('/timeline/buckets') | ||
.set('Authorization', `Bearer ${timeBucketUser.accessToken}`) | ||
.query({ size: TimeBucketSize.Month, withPartners: true, isFavorite: false }); | ||
|
||
expect(req2.status).toBe(400); | ||
expect(req2.body).toEqual(errorDto.badRequest()); | ||
}); | ||
|
||
it('should return error if time bucket is requested with partners asset and trash', async () => { | ||
const req = await request(app) | ||
.get('/timeline/buckets') | ||
.set('Authorization', `Bearer ${user.accessToken}`) | ||
.query({ size: TimeBucketSize.Month, withPartners: true, isTrashed: true }); | ||
|
||
expect(req.status).toBe(400); | ||
expect(req.body).toEqual(errorDto.badRequest()); | ||
}); | ||
}); | ||
|
||
describe('GET /timeline/bucket', () => { | ||
it('should require authentication', async () => { | ||
const { status, body } = await request(app).get('/timeline/bucket').query({ | ||
size: TimeBucketSize.Month, | ||
timeBucket: '1900-01-01T00:00:00.000Z', | ||
}); | ||
|
||
expect(status).toBe(401); | ||
expect(body).toEqual(errorDto.unauthorized); | ||
}); | ||
|
||
it('should handle 5 digit years', async () => { | ||
const { status, body } = await request(app) | ||
.get('/timeline/bucket') | ||
.query({ size: TimeBucketSize.Month, timeBucket: '+012345-01-01T00:00:00.000Z' }) | ||
.set('Authorization', `Bearer ${timeBucketUser.accessToken}`); | ||
|
||
expect(status).toBe(200); | ||
expect(body).toEqual([]); | ||
}); | ||
|
||
// TODO enable date string validation while still accepting 5 digit years | ||
// it('should fail if time bucket is invalid', async () => { | ||
// const { status, body } = await request(app) | ||
// .get('/timeline/bucket') | ||
// .set('Authorization', `Bearer ${user.accessToken}`) | ||
// .query({ size: TimeBucketSize.Month, timeBucket: 'foo' }); | ||
|
||
// expect(status).toBe(400); | ||
// expect(body).toEqual(errorDto.badRequest); | ||
// }); | ||
|
||
it('should return time bucket', async () => { | ||
const { status, body } = await request(app) | ||
.get('/timeline/bucket') | ||
.set('Authorization', `Bearer ${timeBucketUser.accessToken}`) | ||
.query({ size: TimeBucketSize.Month, timeBucket: '1970-02-10T00:00:00.000Z' }); | ||
|
||
expect(status).toBe(200); | ||
expect(body).toEqual([]); | ||
}); | ||
}); | ||
}); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.