mirror of
https://github.com/immich-app/immich.git
synced 2026-05-19 05:52:14 -04:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 266d6c6444 | |||
| a31a62587a |
@@ -288,7 +288,6 @@ jobs:
|
||||
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
||||
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
|
||||
ENVIRONMENT: ${{ inputs.environment || 'development' }}
|
||||
BUNDLE_ID_SUFFIX: ${{ inputs.environment == 'production' && '' || 'development' }}
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 120
|
||||
FASTLANE_XCODEBUILD_SETTINGS_RETRIES: 6
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package app.alextran.immich.core
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.OperationCanceledException
|
||||
import android.security.KeyChain
|
||||
import app.alextran.immich.NativeBuffer
|
||||
|
||||
@@ -718,6 +718,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share.profile;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -750,7 +751,6 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -801,6 +801,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share.debug;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
@@ -860,6 +861,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -894,7 +896,6 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -924,7 +925,6 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -1080,7 +1080,6 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -1124,7 +1123,6 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -1165,7 +1163,6 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 240;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2W7AC6T8T5;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
|
||||
@@ -31,6 +31,9 @@ import native_video_player
|
||||
BackgroundWorkerFgHostApiSetup.setUp(binaryMessenger: messenger, api: BackgroundWorkerApiImpl())
|
||||
ConnectivityApiSetup.setUp(binaryMessenger: messenger, api: ConnectivityApiImpl())
|
||||
NetworkApiSetup.setUp(binaryMessenger: messenger, api: NetworkApiImpl())
|
||||
|
||||
FlutterMethodChannel(name: "home_widget", binaryMessenger: messenger)
|
||||
.invokeMethod("setAppGroupId", arguments: ["groupId": APP_GROUP])
|
||||
}
|
||||
|
||||
public static func cancelPlugins(with engine: FlutterEngine) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import native_video_player
|
||||
let CLIENT_CERT_LABEL = "app.alextran.immich.client_identity"
|
||||
let HEADERS_KEY = "immich.request_headers"
|
||||
let SERVER_URLS_KEY = "immich.server_urls"
|
||||
let APP_GROUP = "group.app.immich.share"
|
||||
let APP_GROUP = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String
|
||||
let COOKIE_EXPIRY_DAYS: TimeInterval = 400
|
||||
|
||||
enum AuthCookie: CaseIterable {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.app.immich.share</string>
|
||||
<string>$(CUSTOM_GROUP_ID)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.app.immich.share</string>
|
||||
<string>$(CUSTOM_GROUP_ID)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.app.immich.share</string>
|
||||
<string>$(CUSTOM_GROUP_ID)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -2,7 +2,7 @@ import Foundation
|
||||
import SwiftUI
|
||||
import WidgetKit
|
||||
|
||||
let IMMICH_SHARE_GROUP = "group.app.immich.share"
|
||||
let IMMICH_SHARE_GROUP = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String
|
||||
|
||||
enum WidgetError: Error, Codable {
|
||||
case noLogin
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>AppGroupId</key>
|
||||
<string>$(CUSTOM_GROUP_ID)</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.app.immich.share</string>
|
||||
<string>$(CUSTOM_GROUP_ID)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -21,6 +21,7 @@ platform :ios do
|
||||
CODE_SIGN_IDENTITY = "Apple Distribution: FUTO Holdings, Inc. (#{TEAM_ID})"
|
||||
BASE_BUNDLE_ID = "app.alextran.immich"
|
||||
DEV_BUNDLE_ID = "tech.futo.immich.testflight"
|
||||
DEV_GROUP_ID = "group.app.immich.share.testflight"
|
||||
|
||||
# Helper method to get App Store Connect API key
|
||||
def get_api_key
|
||||
@@ -33,6 +34,13 @@ platform :ios do
|
||||
)
|
||||
end
|
||||
|
||||
# Helper method to assemble xcargs with optional CUSTOM_GROUP_ID override
|
||||
def build_xcargs(group_id: nil)
|
||||
args = "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual"
|
||||
args += " CUSTOM_GROUP_ID='#{group_id}'" if group_id
|
||||
args
|
||||
end
|
||||
|
||||
# Helper method to get version from pubspec.yaml
|
||||
def get_version_from_pubspec
|
||||
require 'yaml'
|
||||
@@ -89,7 +97,8 @@ end
|
||||
version_number: nil,
|
||||
profile_name_main:,
|
||||
profile_name_share:,
|
||||
profile_name_widget:
|
||||
profile_name_widget:,
|
||||
group_id: nil
|
||||
)
|
||||
app_identifier = base_bundle_id
|
||||
|
||||
@@ -97,7 +106,7 @@ end
|
||||
if version_number
|
||||
increment_version_number(version_number: version_number)
|
||||
end
|
||||
|
||||
|
||||
# Increment build number
|
||||
increment_build_number(
|
||||
build_number: latest_testflight_build_number(
|
||||
@@ -106,14 +115,14 @@ end
|
||||
) + 1,
|
||||
xcodeproj: "./Runner.xcodeproj"
|
||||
)
|
||||
|
||||
|
||||
# Build the app
|
||||
build_app(
|
||||
scheme: "Runner",
|
||||
workspace: "Runner.xcworkspace",
|
||||
configuration: configuration,
|
||||
export_method: "app-store",
|
||||
xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual",
|
||||
xcargs: build_xcargs(group_id: group_id),
|
||||
export_options: {
|
||||
provisioningProfiles: {
|
||||
"#{app_identifier}" => profile_name_main,
|
||||
@@ -165,7 +174,8 @@ end
|
||||
distribute_external: false,
|
||||
profile_name_main: main_profile_name,
|
||||
profile_name_share: share_profile_name,
|
||||
profile_name_widget: widget_profile_name
|
||||
profile_name_widget: widget_profile_name,
|
||||
group_id: DEV_GROUP_ID
|
||||
)
|
||||
end
|
||||
|
||||
@@ -274,7 +284,7 @@ end
|
||||
configuration: "Release",
|
||||
export_method: "app-store",
|
||||
skip_package_ipa: true,
|
||||
xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual",
|
||||
xcargs: build_xcargs(group_id: DEV_GROUP_ID),
|
||||
export_options: {
|
||||
provisioningProfiles: {
|
||||
DEV_BUNDLE_ID => main_profile_name,
|
||||
|
||||
@@ -30,7 +30,6 @@ const int kTimelineAssetLoadBatchSize = 1024;
|
||||
const int kTimelineAssetLoadOppositeSize = 64;
|
||||
|
||||
// Widget keys
|
||||
const String appShareGroupId = "group.app.immich.share";
|
||||
const String kWidgetAuthToken = "widget_auth_token";
|
||||
const String kWidgetServerEndpoint = "widget_server_url";
|
||||
const String kWidgetCustomHeaders = "widget_custom_headers";
|
||||
|
||||
@@ -197,16 +197,6 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
try {
|
||||
await _db.batch((batch) {
|
||||
for (final asset in data) {
|
||||
// Avoid SqliteException(2067) when server re-issues a new id for
|
||||
// the same (ownerId, checksum). #22522 #27186
|
||||
_enqueueRemoteAssetDedupe(
|
||||
batch,
|
||||
id: asset.id,
|
||||
ownerId: asset.ownerId,
|
||||
checksum: asset.checksum,
|
||||
libraryId: asset.libraryId,
|
||||
);
|
||||
|
||||
final companion = RemoteAssetEntityCompanion(
|
||||
name: Value(asset.originalFileName),
|
||||
type: Value(asset.type.toAssetType()),
|
||||
@@ -246,15 +236,6 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
try {
|
||||
await _db.batch((batch) {
|
||||
for (final asset in data) {
|
||||
// See updateAssetsV1 for why this dedupe is required. #22522 #27186
|
||||
_enqueueRemoteAssetDedupe(
|
||||
batch,
|
||||
id: asset.id,
|
||||
ownerId: asset.ownerId,
|
||||
checksum: asset.checksum,
|
||||
libraryId: asset.libraryId,
|
||||
);
|
||||
|
||||
final companion = RemoteAssetEntityCompanion(
|
||||
name: Value(asset.originalFileName),
|
||||
type: Value(asset.type.toAssetType()),
|
||||
@@ -290,39 +271,6 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/// Queues a DELETE that prunes any stale remote_asset row matching the
|
||||
/// partial UNIQUE index for the incoming asset:
|
||||
/// - libraryId IS NULL -> (owner_id, checksum)
|
||||
/// - libraryId NOT NULL -> (owner_id, library_id, checksum)
|
||||
/// The current id is excluded so a same-id update does not delete itself.
|
||||
void _enqueueRemoteAssetDedupe(
|
||||
Batch batch, {
|
||||
required String id,
|
||||
required String ownerId,
|
||||
required String checksum,
|
||||
required String? libraryId,
|
||||
}) {
|
||||
if (libraryId == null) {
|
||||
batch.deleteWhere(
|
||||
_db.remoteAssetEntity,
|
||||
(row) =>
|
||||
row.ownerId.equals(ownerId) &
|
||||
row.checksum.equals(checksum) &
|
||||
row.libraryId.isNull() &
|
||||
row.id.equals(id).not(),
|
||||
);
|
||||
} else {
|
||||
batch.deleteWhere(
|
||||
_db.remoteAssetEntity,
|
||||
(row) =>
|
||||
row.ownerId.equals(ownerId) &
|
||||
row.checksum.equals(checksum) &
|
||||
row.libraryId.equals(libraryId) &
|
||||
row.id.equals(id).not(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data, {String debugLabel = 'user'}) async {
|
||||
try {
|
||||
await _db.batch((batch) {
|
||||
|
||||
@@ -13,8 +13,4 @@ class WidgetRepository {
|
||||
Future<void> refresh(String iosName, String androidName) async {
|
||||
await HomeWidget.updateWidget(iOSName: iosName, qualifiedAndroidName: androidName);
|
||||
}
|
||||
|
||||
Future<void> setAppGroupId(String appGroupId) async {
|
||||
await HomeWidget.setAppGroupId(appGroupId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ class WidgetService {
|
||||
const WidgetService(this._repository);
|
||||
|
||||
Future<void> writeCredentials(String serverURL, String sessionKey, String? customHeaders) async {
|
||||
await _repository.setAppGroupId(appShareGroupId);
|
||||
await _repository.saveData(kWidgetServerEndpoint, serverURL);
|
||||
await _repository.saveData(kWidgetAuthToken, sessionKey);
|
||||
|
||||
@@ -25,7 +24,6 @@ class WidgetService {
|
||||
}
|
||||
|
||||
Future<void> clearCredentials() async {
|
||||
await _repository.setAppGroupId(appShareGroupId);
|
||||
await _repository.saveData(kWidgetServerEndpoint, "");
|
||||
await _repository.saveData(kWidgetAuthToken, "");
|
||||
await _repository.saveData(kWidgetCustomHeaders, "");
|
||||
|
||||
@@ -28,7 +28,6 @@ SyncAssetV1 _createAsset({
|
||||
String ownerId = 'user-1',
|
||||
int? width,
|
||||
int? height,
|
||||
String? libraryId,
|
||||
}) {
|
||||
return SyncAssetV1(
|
||||
id: id,
|
||||
@@ -46,38 +45,7 @@ SyncAssetV1 _createAsset({
|
||||
height: height,
|
||||
deletedAt: null,
|
||||
duration: null,
|
||||
libraryId: libraryId,
|
||||
livePhotoVideoId: null,
|
||||
stackId: null,
|
||||
thumbhash: null,
|
||||
isEdited: false,
|
||||
);
|
||||
}
|
||||
|
||||
SyncAssetV2 _createAssetV2({
|
||||
required String id,
|
||||
required String checksum,
|
||||
required String fileName,
|
||||
String ownerId = 'user-1',
|
||||
String? libraryId,
|
||||
}) {
|
||||
return SyncAssetV2(
|
||||
id: id,
|
||||
checksum: checksum,
|
||||
originalFileName: fileName,
|
||||
type: AssetTypeEnum.IMAGE,
|
||||
ownerId: ownerId,
|
||||
isFavorite: false,
|
||||
fileCreatedAt: DateTime(2024, 1, 1),
|
||||
fileModifiedAt: DateTime(2024, 1, 1),
|
||||
createdAt: DateTime(2024, 1, 1),
|
||||
localDateTime: DateTime(2024, 1, 1),
|
||||
visibility: AssetVisibility.timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
deletedAt: null,
|
||||
duration: 0,
|
||||
libraryId: libraryId,
|
||||
libraryId: null,
|
||||
livePhotoVideoId: null,
|
||||
stackId: null,
|
||||
thumbhash: null,
|
||||
@@ -272,82 +240,4 @@ void main() {
|
||||
expect(after.backupSelection, equals(BackupSelection.none));
|
||||
});
|
||||
});
|
||||
|
||||
group('SyncStreamRepository - updateAssetsV1 dedupe (#22522 #27186)', () {
|
||||
test('replaces stale row when new id arrives with same (ownerId, checksum) and library is null', () async {
|
||||
await sut.updateUsersV1([_createUser()]);
|
||||
await sut.updateAssetsV1([_createAsset(id: 'old-id', checksum: 'AAA', fileName: 'photo.jpg')]);
|
||||
|
||||
// Server re-issues a new id for the same content (replace-with-upload, immich-go, etc.)
|
||||
await sut.updateAssetsV1([_createAsset(id: 'new-id', checksum: 'AAA', fileName: 'photo.jpg')]);
|
||||
|
||||
final rows = await db.remoteAssetEntity.select().get();
|
||||
expect(rows, hasLength(1));
|
||||
expect(rows.single.id, equals('new-id'));
|
||||
expect(rows.single.checksum, equals('AAA'));
|
||||
});
|
||||
|
||||
test('replaces stale row by (ownerId, libraryId, checksum) when library is not null', () async {
|
||||
await sut.updateUsersV1([_createUser()]);
|
||||
await sut.updateAssetsV1([
|
||||
_createAsset(id: 'old-id', checksum: 'AAA', fileName: 'photo.jpg', libraryId: 'lib-1'),
|
||||
]);
|
||||
|
||||
await sut.updateAssetsV1([
|
||||
_createAsset(id: 'new-id', checksum: 'AAA', fileName: 'photo.jpg', libraryId: 'lib-1'),
|
||||
]);
|
||||
|
||||
final rows = await db.remoteAssetEntity.select().get();
|
||||
expect(rows, hasLength(1));
|
||||
expect(rows.single.id, equals('new-id'));
|
||||
expect(rows.single.libraryId, equals('lib-1'));
|
||||
});
|
||||
|
||||
test('library and non-library rows with same (ownerId, checksum) coexist', () async {
|
||||
await sut.updateUsersV1([_createUser()]);
|
||||
await sut.updateAssetsV1([
|
||||
_createAsset(id: 'lib-row', checksum: 'AAA', fileName: 'photo.jpg', libraryId: 'lib-1'),
|
||||
_createAsset(id: 'main-row', checksum: 'AAA', fileName: 'photo.jpg'),
|
||||
]);
|
||||
|
||||
final rows = await db.remoteAssetEntity.select().get();
|
||||
expect(rows, hasLength(2), reason: 'library NULL and NOT NULL match different partial indexes');
|
||||
expect(rows.map((r) => r.id).toSet(), equals({'lib-row', 'main-row'}));
|
||||
});
|
||||
|
||||
test('different owners with same checksum coexist', () async {
|
||||
await sut.updateUsersV1([_createUser(id: 'user-1')]);
|
||||
await sut.updateUsersV1([_createUser(id: 'user-2')]);
|
||||
await sut.updateAssetsV1([
|
||||
_createAsset(id: 'a-id', checksum: 'AAA', fileName: 'photo.jpg', ownerId: 'user-1'),
|
||||
_createAsset(id: 'b-id', checksum: 'AAA', fileName: 'photo.jpg', ownerId: 'user-2'),
|
||||
]);
|
||||
|
||||
final rows = await db.remoteAssetEntity.select().get();
|
||||
expect(rows, hasLength(2));
|
||||
});
|
||||
|
||||
test('same id arriving again updates in place (no self-delete)', () async {
|
||||
await sut.updateUsersV1([_createUser()]);
|
||||
await sut.updateAssetsV1([_createAsset(id: 'same-id', checksum: 'AAA', fileName: 'photo.jpg')]);
|
||||
|
||||
await sut.updateAssetsV1([_createAsset(id: 'same-id', checksum: 'AAA', fileName: 'renamed.jpg')]);
|
||||
|
||||
final rows = await db.remoteAssetEntity.select().get();
|
||||
expect(rows, hasLength(1));
|
||||
expect(rows.single.id, equals('same-id'));
|
||||
expect(rows.single.name, equals('renamed.jpg'), reason: 'ON CONFLICT(id) DO UPDATE path still works');
|
||||
});
|
||||
|
||||
test('updateAssetsV2 dedupes the same way', () async {
|
||||
await sut.updateUsersV1([_createUser()]);
|
||||
await sut.updateAssetsV2([_createAssetV2(id: 'old-id', checksum: 'AAA', fileName: 'photo.jpg')]);
|
||||
|
||||
await sut.updateAssetsV2([_createAssetV2(id: 'new-id', checksum: 'AAA', fileName: 'photo.jpg')]);
|
||||
|
||||
final rows = await db.remoteAssetEntity.select().get();
|
||||
expect(rows, hasLength(1));
|
||||
expect(rows.single.id, equals('new-id'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user