public void Delete() { var item = new ConfigEnvironment(new EnvironmentIdentifier("Foo", "Bar")); item.ApplyEvent(new ReplayedEvent { DomainEvent = new EnvironmentCreated(new EnvironmentIdentifier("Foo", "Bar")), UtcTime = DateTime.UtcNow, Version = 1 }); item.ApplyEvent(new ReplayedEvent { DomainEvent = new EnvironmentKeysImported(new EnvironmentIdentifier("Foo", "Bar"), new[] { ConfigKeyAction.Set("Jar", "Jar", "Jar", "Jar") }), UtcTime = DateTime.UtcNow, Version = 1 }); item.Delete(); Assert.True(item.Deleted, "item.Deleted"); Assert.False(item.Created, "item.Created"); Assert.Empty(item.Keys); Assert.Empty(item.KeyPaths); }
/// <summary> /// overwrite all existing keys with the ones in <paramref name="keysToImport" /> /// </summary> /// <param name="keysToImport"></param> /// <returns></returns> public IResult ImportKeys(ICollection <ConfigEnvironmentKey> keysToImport) { // copy dict as backup var oldKeys = Keys.ToDictionary(_ => _.Key, _ => _.Value); try { var newKeys = keysToImport.ToDictionary(k => k.Key, k => k, StringComparer.OrdinalIgnoreCase); Created = true; Keys = newKeys; CapturedDomainEvents.Add(new EnvironmentKeysImported( Identifier, newKeys.Values .OrderBy(k => k.Key) .Select(k => ConfigKeyAction.Set(k.Key, k.Value, k.Description, k.Type)) .ToArray())); _keyPaths = null; return(Result.Success()); } catch (Exception) { // restore backup Keys = oldKeys; return(Result.Error("could not import all keys into this environment", ErrorCode.Undefined)); } }
public async Task GetKeysWithoutRoot() { var domainObjectStore = new Mock <IDomainObjectStore>(MockBehavior.Strict); domainObjectStore.Setup(dos => dos.ReplayObject(It.IsAny <ConfigEnvironment>(), It.IsAny <string>())) .ReturnsAsync((ConfigEnvironment str, string id) => { str.ApplyEvent(new ReplayedEvent { DomainEvent = new EnvironmentCreated(new EnvironmentIdentifier("Foo", "Bar")), UtcTime = DateTime.UtcNow, Version = 4710 }); str.ApplyEvent(new ReplayedEvent { DomainEvent = new EnvironmentKeysImported(new EnvironmentIdentifier("Foo", "Bar"), new[] { ConfigKeyAction.Set("Foo", "FooValue"), ConfigKeyAction.Set("Foo/Bar", "BarValue"), ConfigKeyAction.Set("Foo/Bar/Baz", "BazValue") }), UtcTime = DateTime.UtcNow, Version = 4711 }); return(Result.Success(str)); }) .Verifiable(); var eventStore = new Mock <IEventStore>(MockBehavior.Strict); eventStore.Setup(es => es.ReplayEventsAsStream( It.IsAny <Func <(StoredEvent StoredEvent, DomainEventMetadata Metadata), bool> >(), It.IsAny <Func <(StoredEvent StoredEvent, DomainEvent DomainEvent), bool> >(), It.IsAny <int>(), It.IsAny <StreamDirection>(), It.IsAny <long>())) .Returns(Task.CompletedTask); var store = new EnvironmentProjectionStore(eventStore.Object, domainObjectStore.Object, _logger, new ICommandValidator[0]); var result = await store.GetKeys(new EnvironmentKeyQueryParameters { Environment = new EnvironmentIdentifier("Foo", "Bar"), Filter = "Foo/", RemoveRoot = "Foo", Range = QueryRange.All }); Assert.Empty(result.Message); Assert.False(result.IsError, "result.IsError"); Assert.NotEmpty(result.Data); domainObjectStore.Verify(); eventStore.Verify(); }
/// <summary> /// Add or Update existing Variables, and record all events necessary for this action /// </summary> /// <param name="updatedKeys"></param> /// <returns></returns> public IResult ModifyVariables(IDictionary <string, string> updatedKeys) { if (updatedKeys is null || !updatedKeys.Any()) { return(Result.Error("null or empty variables given", ErrorCode.InvalidData)); } // record all new keys to remove in case of exception var recordedAdditions = new List <string>(); // maps key to old value - to revert back in case of exception var recordedUpdates = new Dictionary <string, string>(); try { foreach (var(key, value) in updatedKeys) { if (Variables.ContainsKey(key)) { recordedUpdates[key] = Variables[key]; Variables[key] = value; } else { recordedAdditions.Add(key); Variables[key] = value; } } // all items that have actually been added to or changed in this structure are saved as event // skipping those that may not be there anymore and reducing the Event-Size or skipping the event entirely var recordedChanges = recordedAdditions.Concat(updatedKeys.Keys).ToList(); if (recordedChanges.Any()) { CapturedDomainEvents.Add( new StructureVariablesModified( Identifier, recordedChanges.Select(k => ConfigKeyAction.Set(k, Variables[k])) .ToArray())); } return(Result.Success()); } catch (Exception) { foreach (var addedKey in recordedAdditions) { Variables.Remove(addedKey); } foreach (var(key, value) in recordedUpdates) { Variables[key] = value; } return(Result.Error("could not update all variables for this Structure", ErrorCode.Undefined)); } }
/// <summary> /// compare the source-env with all target-envs and return the necessary actions to reach each target /// </summary> /// <param name="targetEnvironment"></param> /// <param name="sourceEnvironments"></param> /// <returns></returns> private IList <EnvironmentComparison> CompareEnvironments(EnvironmentExport targetEnvironment, IDictionary <EnvironmentIdentifier, DtoConfigKey[]> sourceEnvironments) { var comparisons = new List <EnvironmentComparison>(); try { foreach (var(id, sourceKeys) in sourceEnvironments) { Output.WriteVerboseLine($"comparing '{targetEnvironment.Category}/{targetEnvironment.Name}' <=> '{id}'"); var changedKeys = (Mode & ComparisonMode.Add) != 0 ? targetEnvironment.Keys.Where(key => { // if the given key does not exist in the target environment or is somehow changed // we add it to the list of changed keys for review var sourceKey = sourceKeys.FirstOrDefault(k => k.Key.Equals(key.Key)); // null and "" are treated as equals here return(sourceKey is null || !(sourceKey.Value ?? string.Empty).Equals(key.Value ?? string.Empty) || !(sourceKey.Type ?? string.Empty).Equals(key.Type ?? string.Empty) || !(sourceKey.Description ?? string.Empty).Equals(key.Description ?? string.Empty)); }).ToList() : new List <EnvironmentKeyExport>(); var deletedKeys = (Mode & ComparisonMode.Delete) != 0 ? sourceKeys.Where(sk => { // if any target-key doesn't exist in the source any more, // we add it to the list of deleted keys for review return(targetEnvironment.Keys.All(tk => !tk.Key.Equals(sk.Key))); }).ToList() : new List <DtoConfigKey>(); comparisons.Add(new EnvironmentComparison { Source = new EnvironmentIdentifier(targetEnvironment.Category, targetEnvironment.Name), Target = id, RequiredActions = changedKeys.Select(c => KeepNullProperties ? ConfigKeyAction.Set(c.Key, c.Value, c.Description, c.Type) : ConfigKeyAction.Set(c.Key, c.Value ?? string.Empty, c.Description ?? string.Empty, c.Type ?? string.Empty)) .Concat(deletedKeys.Select(d => ConfigKeyAction.Delete(d.Key))) .ToList() }); } } catch (Exception e) { Output.WriteLine($"error while comparing environments: {e}"); return(new List <EnvironmentComparison>()); } return(comparisons); }
public void CreateSetActionViaShortcut() { var action = ConfigKeyAction.Set("Foo", "Bar"); Assert.NotNull(action); Assert.Equal(ConfigKeyActionType.Set, action.Type); Assert.Equal("Foo", action.Key); Assert.Equal("Bar", action.Value); }
public async Task DeleteKeys() { var domainObjectStore = new Mock <IDomainObjectStore>(MockBehavior.Strict); domainObjectStore.Setup(dos => dos.ReplayObject(It.IsAny <ConfigEnvironment>(), It.IsAny <string>())) .ReturnsAsync((ConfigEnvironment str, string id) => { str.ApplyEvent(new ReplayedEvent { DomainEvent = new EnvironmentCreated(new EnvironmentIdentifier("Foo", "Bar")), UtcTime = DateTime.UtcNow, Version = 4710 }); str.ApplyEvent(new ReplayedEvent { DomainEvent = new EnvironmentKeysImported(new EnvironmentIdentifier("Foo", "Bar"), new[] { ConfigKeyAction.Set("Foo", "FooValue"), ConfigKeyAction.Set("Bar", "BarValue"), ConfigKeyAction.Set("Baz", "BazValue") }), UtcTime = DateTime.UtcNow, Version = 4711 }); return(Result.Success(str)); }) .Verifiable(); var eventStore = new Mock <IEventStore>(MockBehavior.Strict); eventStore.Setup(es => es.ReplayEventsAsStream( It.IsAny <Func <(StoredEvent StoredEvent, DomainEventMetadata Metadata), bool> >(), It.IsAny <Func <(StoredEvent StoredEvent, DomainEvent DomainEvent), bool> >(), It.IsAny <int>(), It.IsAny <StreamDirection>(), It.IsAny <long>())) .Returns(Task.CompletedTask); eventStore.Setup(es => es.WriteEvents(It.IsAny <IList <DomainEvent> >())) .ReturnsAsync(4712) .Verifiable("events not written to stream"); var store = new EnvironmentProjectionStore(eventStore.Object, domainObjectStore.Object, _logger, new ICommandValidator[0]); var result = await store.DeleteKeys(new EnvironmentIdentifier("Foo", "Bar"), new[] { "Bar", "Baz" }); Assert.Empty(result.Message); Assert.False(result.IsError, "result.IsError"); domainObjectStore.Verify(); eventStore.Verify(); }
public void ValidateEnvironmentKeysImported() { var validator = CreateValidator(); var result = validator.ValidateDomainEvent(new EnvironmentKeysImported(new EnvironmentIdentifier("Foo", "Bar"), new[] { ConfigKeyAction.Set("Foo", "Bar", "description", "type") })); Assert.False(result.IsError); }
public void ValidateStructureVariablesModified() { var validator = CreateValidator(); var result = validator.ValidateDomainEvent(new StructureVariablesModified(new StructureIdentifier("Foo", 42), new[] { ConfigKeyAction.Set("Foo", "Bar"), ConfigKeyAction.Delete("Baz") })); Assert.False(result.IsError); }
public void ValidateStructureCreatedVariables() { var validator = CreateValidator(); var result = validator.ValidateDomainEvent( new StructureVariablesModified( new StructureIdentifier("Foo", 42), new[] { ConfigKeyAction.Set(string.Empty, string.Empty), ConfigKeyAction.Set(null, null) })); Assert.True(result.IsError); }
public async Task CompilationFailsWhenStructureNotFound() { var config = new PreparedConfiguration( new ConfigurationIdentifier( new EnvironmentIdentifier("Foo", "Bar"), new StructureIdentifier("Foo", 42), 4711)); var store = new Mock <IDomainObjectStore>(MockBehavior.Strict); store.Setup(s => s.ReplayObject(It.IsAny <ConfigEnvironment>(), It.IsAny <string>(), It.IsAny <long>())) .ReturnsAsync((ConfigEnvironment env, string id, long version) => { env.ApplyEvent(new ReplayedEvent { UtcTime = DateTime.UtcNow, Version = 1, DomainEvent = new EnvironmentKeysImported(env.Identifier, new[] { ConfigKeyAction.Set("Foo", "Bar", "", ""), ConfigKeyAction.Set("Jar", "Jar", "", "") }) }); return(Result.Success(env)); }) .Verifiable("Environment not retrieved"); store.Setup(s => s.ReplayObject(It.IsAny <ConfigStructure>(), It.IsAny <string>(), It.IsAny <long>())) .ReturnsAsync(() => Result.Error <ConfigStructure>(string.Empty, ErrorCode.DbQueryError)) .Verifiable("Structure not retrieved"); var compiler = new Mock <IConfigurationCompiler>(MockBehavior.Strict); var parser = new Mock <IConfigurationParser>(MockBehavior.Strict); var translator = new Mock <IJsonTranslator>(MockBehavior.Strict); var result = await config.Compile(store.Object, compiler.Object, parser.Object, translator.Object); Assert.True(result.IsError); store.Verify(); }
public void ReplayHandlesModifiedVariables() { var item = new ConfigStructure(new StructureIdentifier("FooBar", 42)); item.Create(new Dictionary <string, string> { { "Foo", "Bar" } }, new Dictionary <string, string> { { "Bar", "Baz" } }); item.ApplyEvent(new ReplayedEvent { Version = 1, DomainEvent = new StructureVariablesModified(new StructureIdentifier("FooBar", 42), new[] { ConfigKeyAction.Set("Foo", "BarBarBar") }), UtcTime = DateTime.UtcNow }); Assert.Equal(1, item.CurrentVersion); Assert.Equal("BarBarBar", item.Variables["Foo"]); }
/// <summary> /// add new, or change some of the held keys, and create the appropriate events for that /// </summary> /// <param name="keysToAdd"></param> /// <returns></returns> public IResult UpdateKeys(ICollection <ConfigEnvironmentKey> keysToAdd) { if (keysToAdd is null || !keysToAdd.Any()) { return(Result.Error("null or empty list given", ErrorCode.InvalidData)); } var addedKeys = new List <ConfigEnvironmentKey>(); var updatedKeys = new Dictionary <string, ConfigEnvironmentKey>(); try { foreach (var newEntry in keysToAdd) { if (Keys.ContainsKey(newEntry.Key)) { var oldEntry = Keys[newEntry.Key]; if (oldEntry.Description == newEntry.Description && oldEntry.Type == newEntry.Type && oldEntry.Value == newEntry.Value) { // if the key and all metadata is same before and after the change, // we might as well skip this change altogether continue; } updatedKeys.Add(newEntry.Key, Keys[newEntry.Key]); Keys[newEntry.Key] = newEntry; } else { addedKeys.Add(newEntry); Keys.Add(newEntry.Key, newEntry); } } // all items that have actually been added to or changed in this environment are saved as event // skipping those that may not be there anymore and reducing the Event-Size or skipping the event entirely // --- // updatedKeys maps key => oldValue, so the old value can be restored if something goes wrong // this means we have to get the current/new/overwritten state based on the updatedKeys.Keys var recordedChanges = addedKeys.Concat(updatedKeys.Keys.Select(k => Keys[k])) .ToList(); if (recordedChanges.Any()) { CapturedDomainEvents.Add( new EnvironmentKeysModified( Identifier, recordedChanges.Select(k => ConfigKeyAction.Set(k.Key, k.Value, k.Description, k.Type)) .ToArray())); } _keyPaths = null; return(Result.Success()); } catch (Exception) { foreach (var addedKey in addedKeys) { Keys.Remove(addedKey.Key); } foreach (var(key, value) in updatedKeys) { Keys[key] = value; } return(Result.Error("could not update all keys in the environment", ErrorCode.Undefined)); } }
public async Task NoThrowOnCompilationException() { var config = new PreparedConfiguration( new ConfigurationIdentifier( new EnvironmentIdentifier("Foo", "Bar"), new StructureIdentifier("Foo", 42), 4711)); var store = new Mock <IDomainObjectStore>(MockBehavior.Strict); store.Setup(s => s.ReplayObject(It.IsAny <ConfigEnvironment>(), It.IsAny <string>(), It.IsAny <long>())) .ReturnsAsync((ConfigEnvironment env, string id, long version) => { env.ApplyEvent(new ReplayedEvent { UtcTime = DateTime.UtcNow, Version = 1, DomainEvent = new EnvironmentKeysImported(env.Identifier, new[] { ConfigKeyAction.Set("Foo", "Bar", "", ""), ConfigKeyAction.Set("Jar", "Jar", "", "") }) }); return(Result.Success(env)); }) .Verifiable("Environment not retrieved"); store.Setup(s => s.ReplayObject(It.IsAny <ConfigStructure>(), It.IsAny <string>(), It.IsAny <long>())) .ReturnsAsync((ConfigStructure str, string id, long version) => { str.ApplyEvent(new ReplayedEvent { UtcTime = DateTime.UtcNow, Version = 1, DomainEvent = new StructureCreated(str.Identifier, new Dictionary <string, string> { { "Ref", "{{Foo}}{{Jar}}" } }, new Dictionary <string, string>()) }); return(Result.Success(str)); }) .Verifiable("Structure not retrieved"); var compiler = new Mock <IConfigurationCompiler>(MockBehavior.Strict); compiler.Setup(c => c.Compile(It.IsAny <EnvironmentCompilationInfo>(), It.IsAny <StructureCompilationInfo>(), It.IsAny <IConfigurationParser>())) .Throws <Exception>() .Verifiable(); var parser = new Mock <IConfigurationParser>(MockBehavior.Strict); var translator = new Mock <IJsonTranslator>(MockBehavior.Strict); var result = await config.Compile(store.Object, compiler.Object, parser.Object, translator.Object); Assert.True(result.IsError); store.Verify(); compiler.Verify(); }