diff --git a/src/Umbraco.Abstractions/Models/KeyValue.cs b/src/Umbraco.Abstractions/Models/KeyValue.cs index 4ad906d91e..ba98b577a4 100644 --- a/src/Umbraco.Abstractions/Models/KeyValue.cs +++ b/src/Umbraco.Abstractions/Models/KeyValue.cs @@ -28,12 +28,5 @@ namespace Umbraco.Core.Models get => _value; set => SetPropertyValueAndDetectChanges(value, ref _value, nameof(Value)); } - - /// - public DateTime UpdateDate - { - get => _updateDate; - set => SetPropertyValueAndDetectChanges(value, ref _updateDate, nameof(UpdateDate)); - } } } diff --git a/src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs b/src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs index baae2a1724..0eb2b27eb0 100644 --- a/src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs +++ b/src/Umbraco.Abstractions/Persistence/Repositories/IKeyValueRepository.cs @@ -1,13 +1,8 @@ -namespace Umbraco.Infrastructure.Persistence.Repositories +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories { - public interface IKeyValueRepository + public interface IKeyValueRepository : IReadRepository, IWriteRepository { - void Initialize(); - - string GetValue(string key); - - void SetValue(string key, string value); - - bool TrySetValue(string key, string originalValue, string newValue); } } diff --git a/src/Umbraco.Abstractions/Services/ServiceContext.cs b/src/Umbraco.Abstractions/Services/ServiceContext.cs index f3c95b07f9..c9bdc6d924 100644 --- a/src/Umbraco.Abstractions/Services/ServiceContext.cs +++ b/src/Umbraco.Abstractions/Services/ServiceContext.cs @@ -32,12 +32,13 @@ namespace Umbraco.Core.Services private readonly Lazy _externalLoginService; private readonly Lazy _redirectUrlService; private readonly Lazy _consentService; + private readonly Lazy _keyValueService; private readonly Lazy _contentTypeBaseServiceProvider; /// /// Initializes a new instance of the class with lazy services. /// - public ServiceContext(Lazy publicAccessService, Lazy domainService, Lazy auditService, Lazy localizedTextService, Lazy tagService, Lazy contentService, Lazy userService, Lazy memberService, Lazy mediaService, Lazy contentTypeService, Lazy mediaTypeService, Lazy dataTypeService, Lazy fileService, Lazy localizationService, Lazy packagingService, Lazy serverRegistrationService, Lazy entityService, Lazy relationService, Lazy macroService, Lazy memberTypeService, Lazy memberGroupService, Lazy notificationService, Lazy externalLoginService, Lazy redirectUrlService, Lazy consentService, Lazy contentTypeBaseServiceProvider) + public ServiceContext(Lazy publicAccessService, Lazy domainService, Lazy auditService, Lazy localizedTextService, Lazy tagService, Lazy contentService, Lazy userService, Lazy memberService, Lazy mediaService, Lazy contentTypeService, Lazy mediaTypeService, Lazy dataTypeService, Lazy fileService, Lazy localizationService, Lazy packagingService, Lazy serverRegistrationService, Lazy entityService, Lazy relationService, Lazy macroService, Lazy memberTypeService, Lazy memberGroupService, Lazy notificationService, Lazy externalLoginService, Lazy redirectUrlService, Lazy consentService, Lazy keyValueService, Lazy contentTypeBaseServiceProvider) { _publicAccessService = publicAccessService; _domainService = domainService; @@ -64,6 +65,7 @@ namespace Umbraco.Core.Services _externalLoginService = externalLoginService; _redirectUrlService = redirectUrlService; _consentService = consentService; + _keyValueService = keyValueService; _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; } @@ -99,6 +101,7 @@ namespace Umbraco.Core.Services IServerRegistrationService serverRegistrationService = null, IRedirectUrlService redirectUrlService = null, IConsentService consentService = null, + IKeyValueService keyValueService = null, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider = null) { Lazy Lazy(T service) => service == null ? null : new Lazy(() => service); @@ -129,6 +132,7 @@ namespace Umbraco.Core.Services Lazy(externalLoginService), Lazy(redirectUrlService), Lazy(consentService), + Lazy(keyValueService), Lazy(contentTypeBaseServiceProvider) ); } @@ -258,6 +262,11 @@ namespace Umbraco.Core.Services /// public IConsentService ConsentService => _consentService.Value; + /// + /// Gets the KeyValueService. + /// + public IKeyValueService KeyValueService => _keyValueService.Value; + /// /// Gets the ContentTypeServiceBaseFactory. /// diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs index 0939dd0f71..5318c47a40 100644 --- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs +++ b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs @@ -47,6 +47,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); return composition; } diff --git a/src/Umbraco.Infrastructure/Migrations/Custom/IKeyValueServiceInitialization.cs b/src/Umbraco.Infrastructure/Migrations/Custom/IKeyValueServiceInitialization.cs new file mode 100644 index 0000000000..a863905dc7 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Custom/IKeyValueServiceInitialization.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Persistence; + +namespace Umbraco.Infrastructure.Migrations.Custom +{ + public interface IKeyValueServiceInitialization + { + void PerformInitialMigration(IUmbracoDatabase database); + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/Custom/KeyValueServiceInitialization.cs b/src/Umbraco.Infrastructure/Migrations/Custom/KeyValueServiceInitialization.cs new file mode 100644 index 0000000000..488acf507a --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Custom/KeyValueServiceInitialization.cs @@ -0,0 +1,80 @@ +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Infrastructure.Migrations.Custom +{ + public class KeyValueServiceInitialization : IKeyValueServiceInitialization + { + private readonly ILogger _logger; + + public KeyValueServiceInitialization(ILogger logger) + { + _logger = logger; + } + + public void PerformInitialMigration(IUmbracoDatabase database) + { + var context = new MigrationContext(database, _logger); + var initMigration = new InitialMigration(context); + initMigration.Migrate(); + } + + /// + /// A custom migration that executes standalone during the Initialize phase of the KeyValueService. + /// + internal class InitialMigration : MigrationBase + { + public InitialMigration(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // as long as we are still running 7 this migration will be invoked, + // but due to multiple restarts during upgrades, maybe the table + // exists already + if (TableExists(Constants.DatabaseSchema.Tables.KeyValue)) + return; + + Logger.Info("Creating KeyValue structure."); + + // the locks table was initially created with an identity (auto-increment) primary key, + // but we don't want this, especially as we are about to insert a new row into the table, + // so here we drop that identity + DropLockTableIdentity(); + + // insert the lock object for key/value + Insert.IntoTable(Constants.DatabaseSchema.Tables.Lock).Row(new { id = Constants.Locks.KeyValues, name = "KeyValues", value = 1 }).Do(); + + // create the key-value table + Create.Table().Do(); + } + + private void DropLockTableIdentity() + { + // one cannot simply drop an identity, that requires a bit of work + + // create a temp. id column and copy values + Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("nid").AsInt32().Nullable().Do(); + Execute.Sql("update umbracoLock set nid = id").Do(); + + // drop the id column entirely (cannot just drop identity) + Delete.PrimaryKey("PK_umbracoLock").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + Delete.Column("id").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + + // recreate the id column without identity and copy values + Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("id").AsInt32().Nullable().Do(); + Execute.Sql("update umbracoLock set id = nid").Do(); + + // drop the temp. id column + Delete.Column("nid").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); + + // complete the primary key + Alter.Table(Constants.DatabaseSchema.Tables.Lock).AlterColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoLock").Do(); + } + } + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs index ebe101bbee..eab4049ec9 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs @@ -4,12 +4,11 @@ using System.Linq; using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; -using Umbraco.Core.Migrations; using Umbraco.Core.Models; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; -using Umbraco.Infrastructure.Persistence.Repositories; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -19,56 +18,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement : base(scopeAccessor, AppCaches.NoCache, logger) { } - public void Initialize() - { - var context = new MigrationContext(Database, Logger); - var initMigration = new InitialMigration(context); - initMigration.Migrate(); - } - - public string GetValue(string key) - { - return GetDtoByKey(key)?.Value; - } - - public void SetValue(string key, string value) - { - var dto = GetDtoByKey(key); - if (dto == null) - { - dto = new KeyValueDto - { - Key = key, - Value = value, - UpdateDate = DateTime.Now - }; - - Database.Insert(dto); - } - else - { - UpdateDtoValue(dto, value); - } - } - - public bool TrySetValue(string key, string originalValue, string newValue) - { - var dto = GetDtoByKey(key); - - if (dto == null || dto.Value != originalValue) - return false; - - UpdateDtoValue(dto, newValue); - return true; - } - - private void UpdateDtoValue(KeyValueDto dto, string value) - { - dto.Value = value; - dto.UpdateDate = DateTime.Now; - Database.Update(dto); - } - /// /// Gets a value directly from the database, no scope, nothing. /// @@ -84,6 +33,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return database.FirstOrDefault(sql)?.Value; } + #region Overrides of IReadWriteQueryRepository + + public override void Save(IKeyValue entity) + { + if (Get(entity.Identifier) == null) + PersistNewItem(entity); + else + PersistUpdatedItem(entity); + } + + #endregion + #region Overrides of NPocoRepositoryBase protected override Guid NodeObjectTypeId => throw new NotImplementedException(); @@ -114,16 +75,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override IKeyValue PerformGet(string id) { - var dto = GetDtoByKey(id); + var sql = GetBaseQuery(false).Where(x => x.Key == id); + var dto = Database.Fetch(sql).FirstOrDefault(); return dto == null ? null : Map(dto); } - private KeyValueDto GetDtoByKey(string key) - { - var sql = GetBaseQuery(false).Where(x => x.Key == key); - return Database.Fetch(sql).FirstOrDefault(); - } - protected override IEnumerable PerformGetAll(params string[] ids) { var sql = GetBaseQuery(false).WhereIn(x => x.Key, ids); @@ -170,64 +126,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Value = dto.Value, UpdateDate = dto.UpdateDate, }; - } + } - #endregion - - - /// - /// A custom migration that executes standalone during the Initialize phase of the KeyValueService. - /// - internal class InitialMigration : MigrationBase - { - public InitialMigration(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - // as long as we are still running 7 this migration will be invoked, - // but due to multiple restarts during upgrades, maybe the table - // exists already - if (TableExists(Constants.DatabaseSchema.Tables.KeyValue)) - return; - - Logger.Info("Creating KeyValue structure."); - - // the locks table was initially created with an identity (auto-increment) primary key, - // but we don't want this, especially as we are about to insert a new row into the table, - // so here we drop that identity - DropLockTableIdentity(); - - // insert the lock object for key/value - Insert.IntoTable(Constants.DatabaseSchema.Tables.Lock).Row(new { id = Constants.Locks.KeyValues, name = "KeyValues", value = 1 }).Do(); - - // create the key-value table - Create.Table().Do(); - } - - private void DropLockTableIdentity() - { - // one cannot simply drop an identity, that requires a bit of work - - // create a temp. id column and copy values - Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("nid").AsInt32().Nullable().Do(); - Execute.Sql("update umbracoLock set nid = id").Do(); - - // drop the id column entirely (cannot just drop identity) - Delete.PrimaryKey("PK_umbracoLock").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); - Delete.Column("id").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); - - // recreate the id column without identity and copy values - Alter.Table(Constants.DatabaseSchema.Tables.Lock).AddColumn("id").AsInt32().Nullable().Do(); - Execute.Sql("update umbracoLock set id = nid").Do(); - - // drop the temp. id column - Delete.Column("nid").FromTable(Constants.DatabaseSchema.Tables.Lock).Do(); - - // complete the primary key - Alter.Table(Constants.DatabaseSchema.Tables.Lock).AlterColumn("id").AsInt32().NotNullable().PrimaryKey("PK_umbracoLock").Do(); - } - } + #endregion } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs index e8397ba22a..6985bf78da 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs @@ -132,7 +132,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// This method is backed by an cache /// - public void Save(TEntity entity) + public virtual void Save(TEntity entity) { if (entity.HasIdentity == false) CachePolicy.Create(entity, PersistNewItem); diff --git a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs index dbecd56d18..109f307c98 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs @@ -21,6 +21,7 @@ using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Core.Sync; using IntegerValidator = Umbraco.Core.PropertyEditors.Validators.IntegerValidator; +using Umbraco.Infrastructure.Migrations.Custom; namespace Umbraco.Core.Runtime { @@ -128,6 +129,7 @@ namespace Umbraco.Core.Runtime .Append(); composition.RegisterUnique(factory => new MigrationBuilder(factory)); + composition.RegisterUnique(); // by default, register a noop factory composition.RegisterUnique(); diff --git a/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs b/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs index bdb65091de..6835457f64 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/KeyValueService.cs @@ -1,8 +1,10 @@ using System; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Scoping; -using Umbraco.Infrastructure.Persistence.Repositories; +using Umbraco.Infrastructure.Migrations.Custom; namespace Umbraco.Core.Services.Implement { @@ -11,14 +13,16 @@ namespace Umbraco.Core.Services.Implement private readonly object _initialock = new object(); private readonly IScopeProvider _scopeProvider; private readonly IKeyValueRepository _repository; + private readonly IKeyValueServiceInitialization _initialization; private readonly ILogger _logger; private readonly IUmbracoVersion _umbracoVersion; private bool _initialized; - public KeyValueService(IScopeProvider scopeProvider, IKeyValueRepository repository, ILogger logger, IUmbracoVersion umbracoVersion) + public KeyValueService(IScopeProvider scopeProvider, IKeyValueRepository repository, IKeyValueServiceInitialization initialization, ILogger logger, IUmbracoVersion umbracoVersion) { _scopeProvider = scopeProvider; _repository = repository; + _initialization = initialization; _logger = logger; _umbracoVersion = umbracoVersion; } @@ -51,7 +55,7 @@ namespace Umbraco.Core.Services.Implement using (var scope = _scopeProvider.CreateScope()) { - _repository.Initialize(); + _initialization.PerformInitialMigration(scope.Database); scope.Complete(); } @@ -67,7 +71,7 @@ namespace Umbraco.Core.Services.Implement using (var scope = _scopeProvider.CreateScope()) { - return _repository.GetValue(key); + return _repository.Get(key)?.Value; } } @@ -80,7 +84,23 @@ namespace Umbraco.Core.Services.Implement { scope.WriteLock(Constants.Locks.KeyValues); - _repository.SetValue(key, value); + var keyValue = _repository.Get(key); + if (keyValue == null) + { + keyValue = new KeyValue + { + Identifier = key, + Value = value, + UpdateDate = DateTime.Now, + }; + } + else + { + keyValue.Value = value; + keyValue.UpdateDate = DateTime.Now; + } + + _repository.Save(keyValue); scope.Complete(); } @@ -102,11 +122,16 @@ namespace Umbraco.Core.Services.Implement { scope.WriteLock(Constants.Locks.KeyValues); - if (!_repository.TrySetValue(key, originalValue, newValue)) + var keyValue = _repository.Get(key); + if (keyValue == null || keyValue.Value != originalValue) { return false; } + keyValue.Value = newValue; + keyValue.UpdateDate = DateTime.Now; + _repository.Save(keyValue); + scope.Complete(); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/KeyValueRepositoryTests.cs b/src/Umbraco.Tests/Persistence/Repositories/KeyValueRepositoryTests.cs index 92ebb03a32..50ebc7ff5d 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/KeyValueRepositoryTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/KeyValueRepositoryTests.cs @@ -1,7 +1,9 @@ -using NUnit.Framework; +using System; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; -using Umbraco.Infrastructure.Persistence.Repositories; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -19,8 +21,14 @@ namespace Umbraco.Tests.Persistence.Repositories // Insert new key/value using (var scope = provider.CreateScope()) { + var keyValue = new KeyValue + { + Identifier = "foo", + Value = "bar", + UpdateDate = DateTime.Now, + }; var repo = CreateRepository(provider); - repo.SetValue("foo", "bar"); + repo.Save(keyValue); scope.Complete(); } @@ -28,17 +36,20 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = provider.CreateScope()) { var repo = CreateRepository(provider); - var value = repo.GetValue("foo"); + var keyValue = repo.Get("foo"); scope.Complete(); - Assert.AreEqual("bar", value); + Assert.AreEqual("bar", keyValue.Value); } - // Update new key/value + // Update value using (var scope = provider.CreateScope()) { var repo = CreateRepository(provider); - repo.SetValue("foo", "buzz"); + var keyValue = repo.Get("foo"); + keyValue.Value = "buzz"; + keyValue.UpdateDate = DateTime.Now; + repo.Save(keyValue); scope.Complete(); } @@ -46,10 +57,10 @@ namespace Umbraco.Tests.Persistence.Repositories using (var scope = provider.CreateScope()) { var repo = CreateRepository(provider); - var value = repo.GetValue("foo"); + var keyValue = repo.Get("foo"); scope.Complete(); - Assert.AreEqual("buzz", value); + Assert.AreEqual("buzz", keyValue.Value); } } diff --git a/src/Umbraco.Tests/Services/KeyValueServiceTests.cs b/src/Umbraco.Tests/Services/KeyValueServiceTests.cs new file mode 100644 index 0000000000..5ab29258f5 --- /dev/null +++ b/src/Umbraco.Tests/Services/KeyValueServiceTests.cs @@ -0,0 +1,93 @@ +using System.Threading; +using NUnit.Framework; +using NUnit.Framework.Internal; +using Umbraco.Core.Services; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Services +{ + /// + /// Tests covering methods in the KeyValueService class. + /// This is more of an integration test as it involves multiple layers + /// as well as configuration. + /// + [TestFixture] + [Apartment(ApartmentState.STA)] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class KeyValueServiceTests : TestWithSomeContentBase + { + [Test] + public void GetValue_ForMissingKey_ReturnsNull() + { + // Arrange + var keyValueService = ServiceContext.KeyValueService; + + // Act + var value = keyValueService.GetValue("foo"); + + // Assert + Assert.IsNull(value); + } + + [Test] + public void GetValue_ForExistingKey_ReturnsValue() + { + // Arrange + var keyValueService = ServiceContext.KeyValueService; + keyValueService.SetValue("foo", "bar"); + + // Act + var value = keyValueService.GetValue("foo"); + + // Assert + Assert.AreEqual("bar", value); + } + + [Test] + public void SetValue_ForExistingKey_SavesValue() + { + // Arrange + var keyValueService = ServiceContext.KeyValueService; + keyValueService.SetValue("foo", "bar"); + + // Act + keyValueService.SetValue("foo", "buzz"); + var value = keyValueService.GetValue("foo"); + + // Assert + Assert.AreEqual("buzz", value); + } + + [Test] + public void TrySetValue_ForExistingKeyWithProvidedValue_ReturnsTrueAndSetsValue() + { + // Arrange + var keyValueService = ServiceContext.KeyValueService; + keyValueService.SetValue("foo", "bar"); + + // Act + var result = keyValueService.TrySetValue("foo", "bar", "buzz"); + var value = keyValueService.GetValue("foo"); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual("buzz", value); + } + + [Test] + public void TrySetValue_ForExistingKeyWithoutProvidedValue_ReturnsFalseAndDoesNotSetValue() + { + // Arrange + var keyValueService = ServiceContext.KeyValueService; + keyValueService.SetValue("foo", "bar"); + + // Act + var result = keyValueService.TrySetValue("foo", "bang", "buzz"); + var value = keyValueService.GetValue("foo"); + + // Assert + Assert.IsFalse(result); + Assert.AreEqual("bar", value); + } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 92b9dd0ad2..8e884a5937 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -21,6 +21,7 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; +using Umbraco.Infrastructure.Migrations.Custom; using Umbraco.Tests.TestHelpers.Stubs; using Current = Umbraco.Web.Composing.Current; @@ -193,6 +194,7 @@ namespace Umbraco.Tests.TestHelpers var tagService = GetLazyService(factory, c => new TagService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var redirectUrlService = GetLazyService(factory, c => new RedirectUrlService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var consentService = GetLazyService(factory, c => new ConsentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); + var keyValueService = GetLazyService(factory, c => new KeyValueService(scopeProvider, GetRepo(c), Mock.Of(), logger, umbracoVersion)); var contentTypeServiceBaseFactory = GetLazyService(factory, c => new ContentTypeBaseServiceProvider(factory.GetInstance(),factory.GetInstance(),factory.GetInstance())); return new ServiceContext( @@ -221,6 +223,7 @@ namespace Umbraco.Tests.TestHelpers externalLoginService, redirectUrlService, consentService, + keyValueService, contentTypeServiceBaseFactory); } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 1527a8d4cc..46baf94644 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -149,6 +149,7 @@ +