Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
namespace Umbraco.Cms.Core.Persistence.Repositories;

/// <summary>
/// Defines repository methods for querying property type usage.
/// </summary>
public interface IPropertyTypeUsageRepository
{
/// <summary>
/// Determines whether there are any saved property values for the specified content type and property alias.
/// </summary>
Task<bool> HasSavedPropertyValuesAsync(Guid contentTypeKey, string propertyAlias);

/// <summary>
/// Determines whether a content type with the specified unique identifier exists.
/// </summary>
Task<bool> ContentTypeExistAsync(Guid contentTypeKey);
}
3 changes: 3 additions & 0 deletions src/Umbraco.Core/Services/IPropertyTypeUsageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace Umbraco.Cms.Core.Services;

/// <summary>
/// Defines service methods for querying property type usage.
/// </summary>
public interface IPropertyTypeUsageService
{
/// <summary>
Expand Down
10 changes: 8 additions & 2 deletions src/Umbraco.Core/Services/PropertyTypeUsageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,25 @@

namespace Umbraco.Cms.Core.Services;

/// <inheritdoc/>
public class PropertyTypeUsageService : IPropertyTypeUsageService
{
private readonly IPropertyTypeUsageRepository _propertyTypeUsageRepository;
private readonly IContentTypeService _contentTypeService;
private readonly ICoreScopeProvider _scopeProvider;

// TODO (V18): Remove IContentTypeService parameter from constructor.

/// <summary>
/// Initializes a new instance of the <see cref="PropertyTypeUsageService"/> class.
/// </summary>
public PropertyTypeUsageService(
IPropertyTypeUsageRepository propertyTypeUsageRepository,
#pragma warning disable IDE0060 // Remove unused parameter
IContentTypeService contentTypeService,
#pragma warning restore IDE0060 // Remove unused parameter
ICoreScopeProvider scopeProvider)
{
_propertyTypeUsageRepository = propertyTypeUsageRepository;
_contentTypeService = contentTypeService;
_scopeProvider = scopeProvider;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

using NPoco;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Persistence.Repositories;
Expand All @@ -8,28 +7,26 @@

namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;

/// <inheritdoc/>
internal sealed class PropertyTypeUsageRepository : IPropertyTypeUsageRepository
{
private static readonly Guid?[] NodeObjectTypes = new Guid?[]
{
private static readonly List<Guid> _nodeObjectTypes =
[
Constants.ObjectTypes.DocumentType, Constants.ObjectTypes.MediaType, Constants.ObjectTypes.MemberType,
};
];

private readonly IScopeAccessor _scopeAccessor;

public PropertyTypeUsageRepository(IScopeAccessor scopeAccessor)
{
_scopeAccessor = scopeAccessor;
}
/// <summary>
/// Initializes a new instance of the <see cref="PropertyTypeUsageRepository"/> class.
/// </summary>
public PropertyTypeUsageRepository(IScopeAccessor scopeAccessor) => _scopeAccessor = scopeAccessor;

/// <inheritdoc/>
public Task<bool> HasSavedPropertyValuesAsync(Guid contentTypeKey, string propertyAlias)
{
IUmbracoDatabase? database = _scopeAccessor.AmbientScope?.Database;

if (database is null)
{
throw new InvalidOperationException("A scope is required to query the database");
}
IUmbracoDatabase? database = _scopeAccessor.AmbientScope?.Database
?? throw new InvalidOperationException("A scope is required to query the database");

Sql<ISqlContext> selectQuery = database.SqlContext.Sql()
.SelectAll()
Expand All @@ -47,26 +44,21 @@ public Task<bool> HasSavedPropertyValuesAsync(Guid contentTypeKey, string proper
return Task.FromResult(database.ExecuteScalar<bool>(hasValuesQuery));
}

/// <inheritdoc/>
public Task<bool> ContentTypeExistAsync(Guid contentTypeKey)
{
IUmbracoDatabase? database = _scopeAccessor.AmbientScope?.Database;

if (database is null)
{
throw new InvalidOperationException("A scope is required to query the database");
}
IUmbracoDatabase? database = _scopeAccessor.AmbientScope?.Database
?? throw new InvalidOperationException("A scope is required to query the database");

Sql<ISqlContext> selectQuery = database.SqlContext.Sql()
.SelectAll()
.From<NodeDto>("n")
.Where<NodeDto>(n => n.UniqueId == contentTypeKey, "n")
.Where<NodeDto>(n => NodeObjectTypes.Contains(n.NodeObjectType), "n");
.WhereIn<NodeDto>(n => n.NodeObjectType, _nodeObjectTypes, "n");

Sql<ISqlContext> hasValuesQuery = database.SqlContext.Sql()
.SelectAnyIfExists(selectQuery);

return Task.FromResult(database.ExecuteScalar<bool>(hasValuesQuery));
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public async Task Setup()

protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.InternalServerError
ExpectedStatusCode = HttpStatusCode.OK
};

protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
Expand Down Expand Up @@ -58,7 +58,7 @@ public async Task Setup()
protected override async Task<HttpResponseMessage> ClientRequest()
{
HealthCheckActionRequestModel healthCheckActionRequest =
new() { HealthCheck = new ReferenceByIdModel(_dataIntegrityHealthCheckId), ValueRequired = false };
new() { HealthCheck = new ReferenceByIdModel(_dataIntegrityHealthCheckId), ValueRequired = false, Alias = "fixContentPaths" };
return await Client.PostAsync(Url, JsonContent.Create(healthCheckActionRequest));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class AllLogViewerControllerTests : ManagementApiUserGroupTestBase<AllLog
// We get the InternalServerError for the admin because it has access, but there is no log file to view
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.InternalServerError
ExpectedStatusCode = HttpStatusCode.OK
};

protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class AllMessageTemplateLogViewerControllerTests : ManagementApiUserGroup
// We get the InternalServerError for the admin because it has access, but there is no log file to view
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.InternalServerError
ExpectedStatusCode = HttpStatusCode.OK
};

protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class LogLevelCountLogViewerControllerTests : ManagementApiUserGroupTestB
// We get the InternalServerError for the admin because it has access, but there is no log file to view
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.InternalServerError
ExpectedStatusCode = HttpStatusCode.OK
};

protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class ValidateLogFileSizeLogViewerControllerTests: ManagementApiUserGroup
// We get the InternalServerError for the admin because it has access, but there is no log file to view
protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.InternalServerError
ExpectedStatusCode = HttpStatusCode.OK
};

protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public async Task Setup()

protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.InternalServerError
ExpectedStatusCode = HttpStatusCode.OK
};

protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public async Task SetUp()

protected override UserGroupAssertionModel AdminUserGroupAssertionModel => new()
{
ExpectedStatusCode = HttpStatusCode.InternalServerError,
ExpectedStatusCode = HttpStatusCode.InternalServerError, // We expect an error here because email sending is not configured in these tests.
};

protected override UserGroupAssertionModel EditorUserGroupAssertionModel => new()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.

using System;
using NUnit.Framework;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
Expand All @@ -11,6 +10,8 @@ namespace Umbraco.Cms.Tests.Integration.Testing;

public abstract class UmbracoIntegrationTestWithContent : UmbracoIntegrationTest
{
protected const string TextpageContentTypeKey = "1D3A8E6E-2EA9-4CC1-B229-1AEE19821522";

protected const string TextpageKey = "B58B3AD4-62C2-4E27-B1BE-837BD7C533E0";
protected const string SubPageKey = "07EABF4A-5C62-4662-9F2A-15BBB488BCA5";
protected const string SubPage2Key = "0EED78FC-A6A8-4587-AB18-D3AFE212B1C4";
Expand Down Expand Up @@ -48,7 +49,7 @@ public virtual void CreateTestData()
// Create and Save ContentType "umbTextpage" -> 1051 (template), 1052 (content type)
ContentType =
ContentTypeBuilder.CreateSimpleContentType("umbTextpage", "Textpage", defaultTemplateId: template.Id);
ContentType.Key = new Guid("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522");
ContentType.Key = new Guid(TextpageContentTypeKey);
ContentTypeService.Save(ContentType);

// Create and Save Content "Homepage" based on "umbTextpage" -> 1053
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;

namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services;

[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
internal sealed class PropertyTypeUsageServiceTests : UmbracoIntegrationTestWithContent
{
private IPropertyTypeUsageService PropertyTypeUsageService => GetRequiredService<IPropertyTypeUsageService>();

[TestCase(TextpageContentTypeKey, "title", true, true, PropertyTypeOperationStatus.Success)]
[TestCase("1D3A8E6E-2EA9-4CC1-B229-1AEE19821523", "title", false, false, PropertyTypeOperationStatus.ContentTypeNotFound)]
[TestCase(TextpageContentTypeKey, "missingProperty", true, false, PropertyTypeOperationStatus.Success)]
public async Task Can_Check_For_Saved_Property_Values(Guid contentTypeKey, string propertyAlias, bool expectedSuccess, bool expectedResult, PropertyTypeOperationStatus expectedOperationStatus)
{
Attempt<bool, PropertyTypeOperationStatus> resultAttempt = await PropertyTypeUsageService.HasSavedPropertyValuesAsync(contentTypeKey, propertyAlias);
Assert.AreEqual(expectedSuccess, resultAttempt.Success);
Assert.AreEqual(expectedResult, resultAttempt.Result);
Assert.AreEqual(expectedOperationStatus, resultAttempt.Status);
}

Check warning on line 25 in tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/PropertyTypeUsageServiceTests.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (release/17.0)

❌ New issue: Excess Number of Function Arguments

Can_Check_For_Saved_Property_Values has 5 arguments, max arguments = 4. This function has too many arguments, indicating a lack of encapsulation. Avoid adding more arguments.
}
Loading