diff --git a/lib/src/mock_supabase_http_client.dart b/lib/src/mock_supabase_http_client.dart index b96ea97..0350a1b 100644 --- a/lib/src/mock_supabase_http_client.dart +++ b/lib/src/mock_supabase_http_client.dart @@ -270,7 +270,8 @@ class MockSupabaseHttpClient extends BaseClient { } else if (request.method == 'POST') { final preferHeader = request.headers['Prefer']; if (preferHeader != null && - preferHeader.contains('resolution=merge-duplicates')) { + (preferHeader.contains('resolution=merge-duplicates') || + preferHeader.contains('resolution=ignore-duplicates'))) { requestType = RequestType.upsert; } else { requestType = RequestType.insert; @@ -478,30 +479,41 @@ class MockSupabaseHttpClient extends BaseClient { .map((e) => e.trim()) .toList(); + // Rows that conflict are left untouched when the caller asked for + // `ignoreDuplicates`, and merged into otherwise + final ignoreDuplicates = + request.headers['Prefer']?.contains('resolution=ignore-duplicates') ?? + false; + // Upsert each item - final results = items.map((item) { + final results = >[]; + for (final item in items) { // Check if all onConflictColumns are set in item - final shouldUpdate = + final hasConflictTarget = onConflictColumns.every((column) => item[column] != null); - if (shouldUpdate) { + if (hasConflictTarget) { // Find the index for an item that matches all onConflictColumns final index = _database[tableKey]!.indexWhere((dbItem) => onConflictColumns .every((column) => dbItem[column] == item[column])); if (index != -1) { + if (ignoreDuplicates) { + continue; + } // Update the item in the database _database[tableKey]![index] = { ..._database[tableKey]![index], ...item }; - return _database[tableKey]![index]; + results.add(_database[tableKey]![index]); + continue; } } _database[tableKey]!.add(item); - return item; - }).toList(); + results.add(item); + } return _createResponse(results, request: request); } diff --git a/test/mock_supabase_http_client_test.dart b/test/mock_supabase_http_client_test.dart index b2234e2..8b8ab8a 100644 --- a/test/mock_supabase_http_client_test.dart +++ b/test/mock_supabase_http_client_test.dart @@ -104,6 +104,61 @@ void main() { expect(postsAfterUpdate.first, updatedData); }); + test('Upsert with ignoreDuplicates leaves the conflicting row untouched', + () async { + const table = 'users'; + await mockSupabase.from(table).insert({'id': 1, 'name': 'John Doe'}); + + await mockSupabase.from(table).upsert( + {'id': 1, 'name': 'James Bond'}, + ignoreDuplicates: true, + ); + + final users = await mockSupabase.from(table).select(); + expect(users.length, 1); + expect(users.first, {'id': 1, 'name': 'John Doe'}); + }); + + test( + 'Upsert with ignoreDuplicates and an onConflict column leaves the ' + 'conflicting row untouched', () async { + const table = 'users'; + await mockSupabase.from(table).insert({'user_id': 1, 'name': 'John Doe'}); + + await mockSupabase.from(table).upsert( + {'user_id': 1, 'name': 'James Bond'}, + onConflict: 'user_id', + ignoreDuplicates: true, + ); + + final users = await mockSupabase.from(table).select(); + expect(users.length, 1); + expect(users.first, {'user_id': 1, 'name': 'John Doe'}); + }); + + test('Upsert with ignoreDuplicates inserts non-conflicting rows', () async { + const table = 'users'; + await mockSupabase.from(table).insert({'id': 1, 'name': 'John Doe'}); + + final inserted = await mockSupabase.from(table).upsert( + [ + {'id': 1, 'name': 'James Bond'}, + {'id': 2, 'name': 'Jane Doe'}, + ], + ignoreDuplicates: true, + ).select(); + + expect(inserted, [ + {'id': 2, 'name': 'Jane Doe'} + ]); + final users = + await mockSupabase.from(table).select().order('id', ascending: true); + expect(users, [ + {'id': 1, 'name': 'John Doe'}, + {'id': 2, 'name': 'Jane Doe'}, + ]); + }); + test('Upsert then select', () async { // Test upserting a record await mockSupabase