Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(webAPI): Add legacy HTML support for MainContentReference #1256

Merged
merged 6 commits into from
Oct 9, 2024
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
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);
oskogstad marked this conversation as resolved.
Show resolved Hide resolved
}
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
oskogstad marked this conversation as resolved.
Show resolved Hide resolved
/// </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);
}
}
Loading