// This file is Part of CalDavSynchronizer (http://outlookcaldavsynchronizer.sourceforge.net/) // Copyright (c) 2015 Gerhard Zehetbauer // Copyright (c) 2015 Alexander Nimmervoll // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using CalDavSynchronizer.Contracts; using NUnit.Framework; namespace CalDavSynchronizer.IntegrationTests.TestBase { [TestFixture] public abstract class GenericTwoWayTestBase where TSynchronizer : TestSynchronizerBase { protected abstract IEqualityComparer AIdComparer { get; } protected abstract IEqualityComparer BIdComparer { get; } protected abstract IReadOnlyList<(TAId AId, TBId BId)> GetRelations(); protected abstract Task> CreateInA(IEnumerable contents); protected abstract Task> CreateInB(IEnumerable contents); protected abstract Task UpdateInA(IEnumerable<(TAId Id, string Content)> updates); protected abstract Task UpdateInB(IEnumerable<(TBId Id, string Content)> updates); protected abstract Task> GetFromA(ICollection ids); protected abstract Task> GetFromB(ICollection ids); protected virtual TimeSpan PreSyncSleepTime => TimeSpan.Zero; protected abstract Task DeleteInA(IEnumerable ids); protected abstract Task DeleteInB(IEnumerable ids); protected TSynchronizer Synchronizer { get; private set; } protected abstract Options GetOptions(); protected abstract TSynchronizer CreateSynchronizer(Options options); protected async Task InitializeSynchronizer(int? chunkSize, bool useWebDavCollectionSync) { var options = GetOptions(); options.ChunkSize = chunkSize ?? 1000; // Some profiles use chunks even if it disabled, because the server forces that options.IsChunkedSynchronizationEnabled = chunkSize.HasValue; options.UseWebDavCollectionSync = useWebDavCollectionSync; Synchronizer = CreateSynchronizer(options); await Synchronizer.Initialize(); } public virtual async Task Test(int? chunkSize, int itemsPerOperation, bool useWebDavCollectionSync) { await InitializeSynchronizer(null, false); Synchronizer.ClearCache(); await Synchronizer.ClearEventRepositoriesAndCache(); await InitializeSynchronizer(chunkSize, useWebDavCollectionSync); // Set maximum alloed open items to chunksize+1, since the synchronizer builds chunks with operations that load an entity and keep it open // A delete operation in a Outlook repository will open an entity and release it immediately var maximumOpenItemsPerType = chunkSize + 1; var matchingItems = Enumerable.Range(1, itemsPerOperation * 3).Select(i => $"Item {i:000}").ToArray(); var nextName = matchingItems.Length + 1; var itemsJustInA = Enumerable.Range(0, itemsPerOperation).Select(i => $"Item {nextName++:000}").ToArray(); var itemsJustInB = Enumerable.Range(0, itemsPerOperation).Select(i => $"Item {nextName++:000}").ToArray(); Assert.That( (await CreateInA(matchingItems.Union(itemsJustInA))).Count, Is.EqualTo(matchingItems.Length + itemsJustInA.Length)); Assert.That( (await CreateInB(matchingItems.Union(itemsJustInB))).Count, Is.EqualTo(matchingItems.Length + itemsJustInB.Length)); await Task.Delay(PreSyncSleepTime); await Synchronizer.SynchronizeAndCheck( unchangedA: itemsPerOperation * 3, addedA: itemsPerOperation, changedA: 0, deletedA: 0, unchangedB: itemsPerOperation * 3, addedB: itemsPerOperation, changedB: 0, deletedB: 0, createA: itemsPerOperation, updateA: 0, deleteA: 0, createB: itemsPerOperation, updateB: 0, deleteB: 0, ordinalOfReportToCheck: OrdinalOfReportToCheck, maximumOpenItemsPerType: maximumOpenItemsPerType); var relations = GetRelations(); Assert.That(relations.Count, Is.EqualTo(itemsPerOperation * 5)); var aUpdates = new HashSet(AIdComparer); var bUpdates = new HashSet(BIdComparer); var aDeletes = new HashSet(AIdComparer); var bDeletes = new HashSet(BIdComparer); var notUpdated = new HashSet<(TAId AId, TBId BId)>(new RelationEqualityComparer(AIdComparer, BIdComparer)); for (var i = 0; i < relations.Count; i++) { var relation = relations[i]; if (i % 5 == 0) { aUpdates.Add(relation.AId); } else if (i % 5 == 1) { bUpdates.Add(relation.BId); } else if (i % 5 == 2) { aDeletes.Add((relation.AId)); } else if (i % 5 == 3) { bDeletes.Add(relation.BId); } else { notUpdated.Add(relation); } } Assert.That(aUpdates.Count, Is.EqualTo(itemsPerOperation)); Assert.That(bUpdates.Count, Is.EqualTo(itemsPerOperation)); Assert.That(aDeletes.Count, Is.EqualTo(itemsPerOperation)); Assert.That(bDeletes.Count, Is.EqualTo(itemsPerOperation)); Assert.That(notUpdated.Count, Is.EqualTo(itemsPerOperation)); const string commonUpdatePrefix = "Upd"; const string aUpdatePrefix = commonUpdatePrefix + " in A "; const string bUpdatePrefix = commonUpdatePrefix + " in B "; var aEntitiesToUpdate = await GetFromA(aUpdates); Assert.That(aEntitiesToUpdate.Count, Is.EqualTo(aUpdates.Count)); await UpdateInA(aEntitiesToUpdate.Select(u => (u.Item1, aUpdatePrefix + u.Name))); var bEntitiesToUpdate = await GetFromB(bUpdates); Assert.That(bEntitiesToUpdate.Count, Is.EqualTo(bUpdates.Count)); await UpdateInB(bEntitiesToUpdate.Select(u => (u.Item1, bUpdatePrefix + u.Name))); await DeleteInA(aDeletes); await DeleteInB(bDeletes); await Task.Delay(PreSyncSleepTime); await Synchronizer.SynchronizeAndCheck( unchangedA: itemsPerOperation * 3, addedA: 0, changedA: itemsPerOperation, deletedA: itemsPerOperation, unchangedB: itemsPerOperation * 3, addedB: 0, changedB: itemsPerOperation, deletedB: itemsPerOperation, createA: 0, updateA: itemsPerOperation, deleteA: itemsPerOperation, createB: 0, updateB: itemsPerOperation, deleteB: itemsPerOperation, ordinalOfReportToCheck: OrdinalOfReportToCheck, maximumOpenItemsPerType: maximumOpenItemsPerType); var relations2 = GetRelations(); Assert.That(relations2.Count, Is.EqualTo(itemsPerOperation * 3)); var aContentsById = (await GetFromA(relations2.Select(r => r.AId).ToArray())).ToDictionary(e => e.Id, e => e.Name, AIdComparer); var bContentsById = (await GetFromB(relations2.Select(r => r.BId).ToArray())).ToDictionary(e => e.Id, e => e.Name, BIdComparer); foreach (var relation in relations2) { Assert.That(aDeletes.Contains(relation.AId), Is.False); Assert.That(bDeletes.Contains(relation.BId), Is.False); var aContent = aContentsById[relation.AId]; var bContent = bContentsById[relation.BId]; if (aUpdates.Remove(relation.AId)) { Assert.That(aContent, Does.StartWith(aUpdatePrefix)); Assert.That(bContent, Does.StartWith(aUpdatePrefix)); } else if (bUpdates.Remove(relation.BId)) { Assert.That(aContent, Does.StartWith(bUpdatePrefix)); Assert.That(bContent, Does.StartWith(bUpdatePrefix)); } else if (notUpdated.Remove(relation)) { Assert.That(aContent, Does.Not.StartWith(commonUpdatePrefix)); Assert.That(bContent, Does.Not.StartWith(commonUpdatePrefix)); } } Assert.That(aUpdates.Count, Is.EqualTo(0)); Assert.That(bUpdates.Count, Is.EqualTo(0)); Assert.That(notUpdated.Count, Is.EqualTo(0)); } protected virtual int? OrdinalOfReportToCheck => null; class RelationEqualityComparer : IEqualityComparer<(TAId AId, TBId BId)> { private readonly IEqualityComparer _aIdComparer; private readonly IEqualityComparer _bIdComparer; public RelationEqualityComparer(IEqualityComparer aIdComparer, IEqualityComparer bIdComparer) { _aIdComparer = aIdComparer ?? throw new ArgumentNullException(nameof(aIdComparer)); _bIdComparer = bIdComparer ?? throw new ArgumentNullException(nameof(bIdComparer)); } public bool Equals((TAId AId, TBId BId) x, (TAId AId, TBId BId) y) { return _aIdComparer.Equals(x.AId, y.AId) && _bIdComparer.Equals(x.BId, y.BId); } public int GetHashCode((TAId AId, TBId BId) obj) { return _aIdComparer.GetHashCode(obj.AId) * 397 ^ _bIdComparer.GetHashCode(obj.BId); } } } }