Skip to content

Commit

Permalink
feat(webAPI): Add legacy HTML support for MainContentReference (#1256)
Browse files Browse the repository at this point in the history
<!--- Provide a general summary of your changes in the Title above -->

## Description

<!--- Describe your changes in detail -->

## Related Issue(s)

- #1255

## Verification

- [ ] **Your** code builds clean without any errors or warnings
- [ ] Manual testing done (required)
- [ ] Relevant automated test added (if you find this hard, leave it and
we'll help out)

## Documentation

- [ ] Documentation is updated (either in `docs`-directory, Altinnpedia
or a separate linked PR in
[altinn-studio-docs.](https://github.com/Altinn/altinn-studio-docs), if
applicable)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
	- Introduced a new media type constant for legacy embeddable HTML.
- Enhanced XML documentation for supported media types in dialog
properties.
	- Added new enum schemas for improved API structure.

- **Bug Fixes**
- Improved validation for dialog creation with specific media types
based on user scopes.

- **Tests**
- Added new test cases to ensure correct dialog creation behavior with
HTML media types.

- **Documentation**
	- Updated API schemas and descriptions in the Swagger documentation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Magnus Sandgren <[email protected]>
  • Loading branch information
oskogstad and MagnusSandgren authored Oct 9, 2024
1 parent 75e0421 commit 482b38a
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 35 deletions.
15 changes: 7 additions & 8 deletions docs/schema/V1/swagger.verified.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@
"properties": {
"mediaType": {
"description": "Media type of the content (plaintext, Markdown). Can also indicate that the content is embeddable.",
"example": "text/plain\ntext/markdown\napplication/vnd.dialogporten.frontchannelembed",
"type": "string"
},
"value": {
Expand Down Expand Up @@ -297,7 +296,7 @@
"additionalProperties": false,
"properties": {
"additionalInfo": {
"description": "Additional information about the dialog, this may contain Markdown.",
"description": "Additional information about the dialog.\nSupported media types: text/plain, text/markdown",
"nullable": true,
"oneOf": [
{
Expand All @@ -306,7 +305,7 @@
]
},
"extendedStatus": {
"description": "Used as the human-readable label used to describe the \u0022ExtendedStatus\u0022 field. Must be text/plain.",
"description": "Used as the human-readable label used to describe the \u0022ExtendedStatus\u0022 field.\nSupported media types: text/plain",
"nullable": true,
"oneOf": [
{
Expand All @@ -315,7 +314,7 @@
]
},
"mainContentReference": {
"description": "Front-channel embedded content. Used to dynamically embed content in the frontend from an external URL.",
"description": "Front-channel embedded content. Used to dynamically embed content in the frontend from an external URL.\nSupported media types: application/vnd.dialogporten.frontchannelembed\u002Bjson;type=markdown",
"nullable": true,
"oneOf": [
{
Expand All @@ -324,7 +323,7 @@
]
},
"senderName": {
"description": "Overridden sender name. If not supplied, assume \u0022org\u0022 as the sender name. Must be text/plain if supplied.",
"description": "Overridden sender name. If not supplied, assume \u0022org\u0022 as the sender name. Must be text/plain if supplied.\nSupported media types: text/plain",
"nullable": true,
"oneOf": [
{
Expand All @@ -333,15 +332,15 @@
]
},
"summary": {
"description": "A short summary of the dialog and its current state. Must be text/plain.",
"description": "A short summary of the dialog and its current state.\nSupported media types: text/plain",
"oneOf": [
{
"$ref": "#/components/schemas/ContentValueDto"
}
]
},
"title": {
"description": "The title of the dialog. Must be text/plain.",
"description": "The title of the dialog.\nSupported media types: text/plain",
"oneOf": [
{
"$ref": "#/components/schemas/ContentValueDto"
Expand Down Expand Up @@ -6474,4 +6473,4 @@
"url": "https://altinn-dev-api.azure-api.net/dialogporten"
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,5 @@ public sealed class ContentValueDto
/// <summary>
/// Media type of the content (plaintext, Markdown). Can also indicate that the content is embeddable.
/// </summary>
/// <example>
/// text/plain
/// text/markdown
/// application/vnd.dialogporten.frontchannelembed
/// </example>
public string MediaType { get; set; } = MediaTypes.PlainText;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Digdir.Domain.Dialogporten.Application.Common.Authorization;
using Digdir.Domain.Dialogporten.Application.Common.Extensions;
using Digdir.Domain.Dialogporten.Application.Common.Extensions.FluentValidation;
Expand All @@ -17,7 +18,6 @@ internal interface IIgnoreOnAssemblyScan;

internal sealed class ContentValueDtoValidator : AbstractValidator<ContentValueDto>, IIgnoreOnAssemblyScan
{
public const string LegacyHtmlMediaType = "text/html";

public ContentValueDtoValidator(DialogTransmissionContentType contentType)
{
Expand Down Expand Up @@ -68,22 +68,15 @@ x.MediaType is not null
.SetValidator(_ => new LocalizationDtosValidator(contentType.MaxLength));
}

[SuppressMessage("Style", "IDE0072:Add missing cases")]
private static string[] GetAllowedMediaTypes(DialogContentType contentType, IUser? user)
{
if (user == null)
{
return contentType.AllowedMediaTypes;
}

if (contentType.Id != DialogContentType.Values.AdditionalInfo)
=> contentType.Id switch
{
return contentType.AllowedMediaTypes;
}

var allowHtmlSupport = user.GetPrincipal().HasScope(Constants.LegacyHtmlScope);

return allowHtmlSupport
? contentType.AllowedMediaTypes.Append(LegacyHtmlMediaType).ToArray()
: contentType.AllowedMediaTypes;
}
DialogContentType.Values.AdditionalInfo when UserHasLegacyHtmlScope(user)
=> contentType.AllowedMediaTypes.Append(MediaTypes.LegacyHtml).ToArray(),
DialogContentType.Values.MainContentReference when UserHasLegacyHtmlScope(user)
=> contentType.AllowedMediaTypes.Append(MediaTypes.LegacyEmbeddableHtml).ToArray(),
_ => contentType.AllowedMediaTypes
};
private static bool UserHasLegacyHtmlScope(IUser? user) => user is not null && user.GetPrincipal().HasScope(Constants.LegacyHtmlScope);
}
Original file line number Diff line number Diff line change
Expand Up @@ -209,32 +209,38 @@ public sealed class CreateDialogDialogTransmissionDto
public sealed class CreateDialogContentDto
{
/// <summary>
/// The title of the dialog. Must be text/plain.
/// The title of the dialog.
/// Supported media types: text/plain
/// </summary>
public ContentValueDto Title { get; set; } = null!;

/// <summary>
/// A short summary of the dialog and its current state. Must be text/plain.
/// A short summary of the dialog and its current state.
/// Supported media types: text/plain
/// </summary>
public ContentValueDto Summary { get; set; } = null!;

/// <summary>
/// Overridden sender name. If not supplied, assume "org" as the sender name. Must be text/plain if supplied.
/// Supported media types: text/plain
/// </summary>
public ContentValueDto? SenderName { get; set; }

/// <summary>
/// Additional information about the dialog, this may contain Markdown.
/// Additional information about the dialog.
/// Supported media types: text/plain, text/markdown
/// </summary>
public ContentValueDto? AdditionalInfo { get; set; }

/// <summary>
/// Used as the human-readable label used to describe the "ExtendedStatus" field. Must be text/plain.
/// Used as the human-readable label used to describe the "ExtendedStatus" field.
/// Supported media types: text/plain
/// </summary>
public ContentValueDto? ExtendedStatus { get; set; }

/// <summary>
/// Front-channel embedded content. Used to dynamically embed content in the frontend from an external URL.
/// Supported media types: application/vnd.dialogporten.frontchannelembed+json;type=markdown
/// </summary>
public ContentValueDto? MainContentReference { get; set; }
}
Expand Down
2 changes: 2 additions & 0 deletions src/Digdir.Domain.Dialogporten.Domain/MediaTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ public static class MediaTypes
{
public const string EmbeddablePrefix = "application/vnd.dialogporten.frontchannelembed";
public const string EmbeddableMarkdown = $"{EmbeddablePrefix}+json;type=markdown";
public const string LegacyEmbeddableHtml = $"{EmbeddablePrefix}+json;type=html";

public const string LegacyHtml = "text/html";
public const string Markdown = "text/markdown";
public const string PlainText = "text/plain";
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations;
using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Queries.Get;
using Digdir.Domain.Dialogporten.Application.Integration.Tests.Common;
using Digdir.Domain.Dialogporten.Domain;
using Digdir.Tool.Dialogporten.GenerateFakeData;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -276,7 +277,7 @@ public async Task Cannot_Create_Transmission_With_Empty_Content_Localization_Val
.Be(2);
}

private const string LegacyHtmlMediaType = ContentValueDtoValidator.LegacyHtmlMediaType;
private const string LegacyHtmlMediaType = MediaTypes.LegacyHtml;

private static ContentValueDto CreateHtmlContentValueDto() => new()
{
Expand Down Expand Up @@ -350,4 +351,62 @@ public async Task Cannot_Create_Title_Content_With_Html_MediaType_With_Correct_S
.Should()
.Be(1);
}

[Fact]
public async Task Cannot_Create_Title_Content_With_Embeddable_Html_MediaType_With_Correct_Scope()
{
// Arrange
var createDialogCommand = DialogGenerator.GenerateSimpleFakeDialog();
createDialogCommand.Content.Title = new ContentValueDto
{
MediaType = MediaTypes.LegacyEmbeddableHtml,
Value = [new LocalizationDto { LanguageCode = "en", Value = "https://external.html" }]
};

var userWithLegacyScope = new IntegrationTestUser([new("scope", Constants.LegacyHtmlScope)]);
Application.ConfigureServiceCollection(services =>
{
services.RemoveAll<IUser>();
services.AddSingleton<IUser>(userWithLegacyScope);
});

// Act
var response = await Application.Send(createDialogCommand);

// Assert
response.TryPickT2(out var validationError, out _).Should().BeTrue();
validationError.Should().NotBeNull();
validationError.Errors
.Count(e => e.AttemptedValue.Equals(MediaTypes.LegacyEmbeddableHtml))
.Should()
.Be(1);
}

[Fact]
public async Task Can_Create_MainContentRef_Content_With_Embeddable_Html_MediaType_With_Correct_Scope()
{
// Arrange
var expectedDialogId = GenerateBigEndianUuidV7();
var createDialogCommand = DialogGenerator.GenerateSimpleFakeDialog(id: expectedDialogId);
createDialogCommand.Content.MainContentReference = new ContentValueDto
{
MediaType = MediaTypes.LegacyEmbeddableHtml,
Value = [new LocalizationDto { LanguageCode = "en", Value = "https://external.html" }]
};

var userWithLegacyScope = new IntegrationTestUser([new("scope", Constants.LegacyHtmlScope)]);
Application.ConfigureServiceCollection(services =>
{
services.RemoveAll<IUser>();
services.AddSingleton<IUser>(userWithLegacyScope);
});

// Act
var response = await Application.Send(createDialogCommand);

// Assert
response.TryPickT0(out var success, out _).Should().BeTrue();
success.Should().NotBeNull();
success.Value.Should().Be(expectedDialogId);
}
}

0 comments on commit 482b38a

Please sign in to comment.