| | | 1 | | using ArturRios.Data.Relational.Core.Entities; |
| | | 2 | | using ArturRios.Data.Relational.Core.Interfaces; |
| | | 3 | | using ArturRios.Output; |
| | | 4 | | |
| | | 5 | | namespace ArturRios.Util.Test.Mock; |
| | | 6 | | |
| | | 7 | | /// <summary> |
| | | 8 | | /// In-memory implementation of <see cref="IRepository{T}"/> for use in tests. |
| | | 9 | | /// Entities are stored in a backing list and identifiers are assigned sequentially starting at <c>1</c>. |
| | | 10 | | /// Lookups that find no matching entity return a failed <see cref="DataOutput{T}"/> carrying an error rather than throw |
| | | 11 | | /// </summary> |
| | | 12 | | /// <typeparam name="T">The entity type handled by the repository.</typeparam> |
| | | 13 | | public class FakeRepository<T> : IRepository<T> where T : Entity |
| | | 14 | | { |
| | 13 | 15 | | private readonly List<T> _items = []; |
| | 13 | 16 | | private long _nextId = 1; |
| | | 17 | | |
| | | 18 | | /// <summary>Exposes the stored entities as a queryable sequence.</summary> |
| | | 19 | | /// <returns>A queryable over every stored entity.</returns> |
| | 1 | 20 | | public IQueryable<T> Query() => _items.AsQueryable(); |
| | | 21 | | |
| | | 22 | | /// <summary>Returns all stored entities.</summary> |
| | | 23 | | /// <returns>A successful output whose data contains every stored entity.</returns> |
| | 2 | 24 | | public DataOutput<IEnumerable<T>> GetAll() => DataOutput<IEnumerable<T>>.New.WithData(_items); |
| | | 25 | | |
| | | 26 | | /// <summary>Returns the entity with the given identifier.</summary> |
| | | 27 | | /// <param name="id">The identifier to look up.</param> |
| | | 28 | | /// <returns> |
| | | 29 | | /// A successful output carrying the matching entity, or a failed output when no stored entity has that identifier. |
| | | 30 | | /// </returns> |
| | | 31 | | public DataOutput<T?> GetById(long id) |
| | 9 | 32 | | { |
| | 17 | 33 | | var item = _items.FirstOrDefault(x => x.Id == id); |
| | | 34 | | |
| | 9 | 35 | | return item is null |
| | 9 | 36 | | ? DataOutput<T?>.New.WithError($"Entity with Id {id} not found") |
| | 9 | 37 | | : DataOutput<T?>.New.WithData(item); |
| | 9 | 38 | | } |
| | | 39 | | |
| | | 40 | | /// <summary>Adds <paramref name="entity"/> to the store, assigning it a fresh identifier.</summary> |
| | | 41 | | /// <param name="entity">The entity to store.</param> |
| | | 42 | | /// <returns>A successful output carrying the identifier assigned to the entity.</returns> |
| | | 43 | | public DataOutput<long> Create(T entity) |
| | 15 | 44 | | { |
| | 15 | 45 | | entity.Id = _nextId++; |
| | | 46 | | |
| | 15 | 47 | | _items.Add(entity); |
| | | 48 | | |
| | 15 | 49 | | return DataOutput<long>.New.WithData(entity.Id); |
| | 15 | 50 | | } |
| | | 51 | | |
| | | 52 | | /// <summary>Adds every entity in <paramref name="entities"/> to the store, assigning each a fresh identifier.</summ |
| | | 53 | | /// <param name="entities">The entities to store.</param> |
| | | 54 | | /// <returns>A successful output carrying the identifiers assigned, in insertion order.</returns> |
| | | 55 | | public DataOutput<IEnumerable<long>> CreateRange(IEnumerable<T> entities) |
| | 1 | 56 | | { |
| | 1 | 57 | | var ids = new List<long>(); |
| | | 58 | | |
| | 7 | 59 | | foreach (var entity in entities) |
| | 2 | 60 | | { |
| | 2 | 61 | | entity.Id = _nextId++; |
| | | 62 | | |
| | 2 | 63 | | _items.Add(entity); |
| | 2 | 64 | | ids.Add(entity.Id); |
| | 2 | 65 | | } |
| | | 66 | | |
| | 1 | 67 | | return DataOutput<IEnumerable<long>>.New.WithData(ids); |
| | 1 | 68 | | } |
| | | 69 | | |
| | | 70 | | /// <summary>Copies the writable properties of <paramref name="entity"/> onto the stored entity with the same identi |
| | | 71 | | /// <param name="entity">The entity carrying the new values.</param> |
| | | 72 | | /// <returns> |
| | | 73 | | /// A successful output carrying the updated stored entity, or a failed output when no stored entity has a matching |
| | | 74 | | /// </returns> |
| | | 75 | | public DataOutput<T> Update(T entity) |
| | 2 | 76 | | { |
| | 3 | 77 | | var existingItem = _items.FirstOrDefault(item => item.Id == entity.Id); |
| | | 78 | | |
| | 2 | 79 | | if (existingItem is null) |
| | 1 | 80 | | { |
| | 1 | 81 | | return DataOutput<T>.New.WithError($"Entity with Id {entity.Id} not found"); |
| | | 82 | | } |
| | | 83 | | |
| | 1 | 84 | | CopyWritableProperties(entity, existingItem); |
| | | 85 | | |
| | 1 | 86 | | return DataOutput<T>.New.WithData(existingItem); |
| | 2 | 87 | | } |
| | | 88 | | |
| | | 89 | | /// <summary>Updates every entity that exists in the store, silently skipping identifiers that are not found.</summa |
| | | 90 | | /// <param name="entities">The entities carrying the new values.</param> |
| | | 91 | | /// <returns>A successful output carrying the stored entities that were updated.</returns> |
| | | 92 | | public DataOutput<IEnumerable<T>> UpdateRange(IEnumerable<T> entities) |
| | 2 | 93 | | { |
| | 2 | 94 | | var updated = new List<T>(); |
| | | 95 | | |
| | 14 | 96 | | foreach (var entity in entities) |
| | 4 | 97 | | { |
| | 9 | 98 | | var existingItem = _items.FirstOrDefault(item => item.Id == entity.Id); |
| | | 99 | | |
| | 4 | 100 | | if (existingItem is null) |
| | 1 | 101 | | { |
| | | 102 | | // Entities that do not exist are silently skipped. |
| | 1 | 103 | | continue; |
| | | 104 | | } |
| | | 105 | | |
| | 3 | 106 | | CopyWritableProperties(entity, existingItem); |
| | 3 | 107 | | updated.Add(existingItem); |
| | 3 | 108 | | } |
| | | 109 | | |
| | 2 | 110 | | return DataOutput<IEnumerable<T>>.New.WithData(updated); |
| | 2 | 111 | | } |
| | | 112 | | |
| | | 113 | | /// <summary>Removes the stored entity with the same identifier as <paramref name="entity"/>.</summary> |
| | | 114 | | /// <param name="entity">The entity to remove.</param> |
| | | 115 | | /// <returns> |
| | | 116 | | /// A successful output carrying the identifier of the removed entity, or a failed output when no stored entity has |
| | | 117 | | /// </returns> |
| | | 118 | | public DataOutput<long> Delete(T entity) |
| | 2 | 119 | | { |
| | 3 | 120 | | var existingItem = _items.FirstOrDefault(item => item.Id == entity.Id); |
| | | 121 | | |
| | 2 | 122 | | if (existingItem is null) |
| | 1 | 123 | | { |
| | 1 | 124 | | return DataOutput<long>.New.WithError($"Entity with Id {entity.Id} not found"); |
| | | 125 | | } |
| | | 126 | | |
| | 1 | 127 | | _items.Remove(existingItem); |
| | | 128 | | |
| | 1 | 129 | | return DataOutput<long>.New.WithData(existingItem.Id); |
| | 2 | 130 | | } |
| | | 131 | | |
| | | 132 | | /// <summary>Removes every stored entity whose identifier is in <paramref name="ids"/>.</summary> |
| | | 133 | | /// <param name="ids">The identifiers to remove.</param> |
| | | 134 | | /// <returns>A successful output carrying the identifiers that were actually removed.</returns> |
| | | 135 | | public DataOutput<IEnumerable<long>> DeleteRange(IEnumerable<long> ids) |
| | 1 | 136 | | { |
| | 1 | 137 | | var idSet = ids.ToHashSet(); |
| | 4 | 138 | | var entities = _items.Where(e => idSet.Contains(e.Id)).ToList(); |
| | | 139 | | |
| | 7 | 140 | | foreach (var entity in entities) |
| | 2 | 141 | | { |
| | 2 | 142 | | _items.Remove(entity); |
| | 2 | 143 | | } |
| | | 144 | | |
| | 3 | 145 | | return DataOutput<IEnumerable<long>>.New.WithData(entities.Select(e => e.Id).ToList()); |
| | 1 | 146 | | } |
| | | 147 | | |
| | | 148 | | private static void CopyWritableProperties(T source, T target) |
| | 4 | 149 | | { |
| | 36 | 150 | | foreach (var prop in typeof(T).GetProperties()) |
| | 12 | 151 | | { |
| | 12 | 152 | | if (!prop.CanWrite || prop.Name == nameof(Entity.Id)) |
| | 4 | 153 | | { |
| | 4 | 154 | | continue; |
| | | 155 | | } |
| | | 156 | | |
| | 8 | 157 | | prop.SetValue(target, prop.GetValue(source)); |
| | 8 | 158 | | } |
| | 4 | 159 | | } |
| | | 160 | | } |