Files
immich/server/src/services/map.service.spec.ts
T
Timon 1fcc2b704b feat(server)!: add isOwned filter to albums API (#28213)
* feat(server)!: add owned filter to albums API

BREAKING CHANGE: GET /albums with no parameters now returns all accessible albums (owned + shared-with-me) instead of only owned albums.

* document tri-state matrix

* web impl

* collapse to single method and handover branching to sql

* dedupe

* verify that owned, shared, and notShared counts are mapped independently from their respective queries

* refactor(server): add select:['id'] overload to albumRepository.getAll

Avoid fetching full album rows (with albumUsers/sharedLinks subqueries) in map.service where only album IDs are needed.

* focus relevant test filters

* fmt

* Revert "verify that owned, shared, and notShared counts are mapped independently from their respective queries"

This reverts commit 47aab458192c766de4662aada5a6841b091d2a80.

* sync sql

* Revert "document tri-state matrix"

This reverts commit a5b2355d0c.

* address review comments

* inline shared condition and return as ternary

* sync sql

* use [...albums].sort

Array.toSorted() is not supported in Chrome 109

* use isShared and isOwned nomenclature

* fix e2e tests

* add params to sql query
2026-05-07 12:13:07 -04:00

109 lines
3.9 KiB
TypeScript

import { MapService } from 'src/services/map.service';
import { AlbumFactory } from 'test/factories/album.factory';
import { AssetFactory } from 'test/factories/asset.factory';
import { AuthFactory } from 'test/factories/auth.factory';
import { PartnerFactory } from 'test/factories/partner.factory';
import { userStub } from 'test/fixtures/user.stub';
import { getForPartner } from 'test/mappers';
import { newTestService, ServiceMocks } from 'test/utils';
describe(MapService.name, () => {
let sut: MapService;
let mocks: ServiceMocks;
beforeEach(() => {
({ sut, mocks } = newTestService(MapService));
});
describe('getMapMarkers', () => {
it('should get geo information of assets', async () => {
const auth = AuthFactory.create();
const asset = AssetFactory.from()
.exif({ latitude: 42, longitude: 69, city: 'city', state: 'state', country: 'country' })
.build();
const marker = {
id: asset.id,
lat: asset.exifInfo.latitude!,
lon: asset.exifInfo.longitude!,
city: asset.exifInfo.city,
state: asset.exifInfo.state,
country: asset.exifInfo.country,
};
mocks.partner.getAll.mockResolvedValue([]);
mocks.map.getMapMarkers.mockResolvedValue([marker]);
const markers = await sut.getMapMarkers(auth, {});
expect(markers).toHaveLength(1);
expect(markers[0]).toEqual(marker);
});
it('should include partner assets', async () => {
const auth = AuthFactory.create();
const partner = PartnerFactory.create({ sharedWithId: auth.user.id });
const asset = AssetFactory.from()
.exif({ latitude: 42, longitude: 69, city: 'city', state: 'state', country: 'country' })
.build();
const marker = {
id: asset.id,
lat: asset.exifInfo.latitude!,
lon: asset.exifInfo.longitude!,
city: asset.exifInfo.city,
state: asset.exifInfo.state,
country: asset.exifInfo.country,
};
mocks.partner.getAll.mockResolvedValue([getForPartner(partner)]);
mocks.map.getMapMarkers.mockResolvedValue([marker]);
const markers = await sut.getMapMarkers(auth, { withPartners: true });
expect(mocks.map.getMapMarkers).toHaveBeenCalledWith(
[auth.user.id, partner.sharedById],
expect.arrayContaining([]),
{ withPartners: true },
);
expect(markers).toHaveLength(1);
expect(markers[0]).toEqual(marker);
});
it('should include assets from shared albums', async () => {
const auth = AuthFactory.create(userStub.user1);
const asset = AssetFactory.from()
.exif({ latitude: 42, longitude: 69, city: 'city', state: 'state', country: 'country' })
.build();
const marker = {
id: asset.id,
lat: asset.exifInfo.latitude!,
lon: asset.exifInfo.longitude!,
city: asset.exifInfo.city,
state: asset.exifInfo.state,
country: asset.exifInfo.country,
};
mocks.partner.getAll.mockResolvedValue([]);
mocks.map.getMapMarkers.mockResolvedValue([marker]);
const album1 = AlbumFactory.create();
const album2 = AlbumFactory.from().albumUser({ userId: userStub.user1.id }).build();
mocks.album.getAllIds.mockResolvedValue([album1.id, album2.id]);
const markers = await sut.getMapMarkers(auth, { withSharedAlbums: true });
expect(markers).toHaveLength(1);
expect(markers[0]).toEqual(marker);
expect(mocks.album.getAllIds).toHaveBeenCalledWith(auth.user.id);
});
});
describe('reverseGeocode', () => {
it('should reverse geocode a location', async () => {
mocks.map.reverseGeocode.mockResolvedValue({ city: 'foo', state: 'bar', country: 'baz' });
await expect(sut.reverseGeocode({ lat: 42, lon: 69 })).resolves.toEqual([
{ city: 'foo', state: 'bar', country: 'baz' },
]);
expect(mocks.map.reverseGeocode).toHaveBeenCalledWith({ latitude: 42, longitude: 69 });
});
});
});