-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Let ModelDeserializer return a Result type instead of nullable * Remove NullDataProcessor and let user registrer multiple processors * Update IDataProcessor interface and support multipart data write * Update v7tov8 script for new IDataProcessor interface Also ignore CS1998 in apps (warning when not awaiting anything in async hooks) * Code review updates * Update src/Altinn.App.Core/Helpers/Serialization/ModelDeserializer.cs Co-authored-by: Vemund Gaukstad <[email protected]> * First draft of tests for controllers * Finish writing tests * Set partyId in token to null * Fix bad case for roles folder in testsetup * Add partyId correctly in test tokens Also add a Dockerfile for running tests on linux * Use proper parsing library to decode multipart/form-data requests * Continue returning changed only changed values from data put. * Fix so that it compiles (still failing tests) * Fix tests by disabeling redirects in applciationfactory client For some reason backend returns 303 See Other when datamodel updates are availible. This is a redirect code and crashed the tests as no Location header was set. * Add more tests * More tests * Update src/Altinn.App.Api/Controllers/DataController.cs Co-authored-by: Vemund Gaukstad <[email protected]> * Fix code smells * Fix tests * Tests OK now? * More tests for model deserializer --------- Co-authored-by: Vemund Gaukstad <[email protected]>
- Loading branch information
Showing
22 changed files
with
1,028 additions
and
224 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
179 changes: 179 additions & 0 deletions
179
cli-tools/altinn-app-cli/v7Tov8/CodeRewriters/IDataProcessorRewriter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
|
||
namespace altinn_app_cli.v7Tov8.CodeRewriters | ||
{ | ||
public class DataProcessorRewriter : CSharpSyntaxRewriter | ||
{ | ||
private readonly SemanticModel semanticModel; | ||
|
||
public DataProcessorRewriter(SemanticModel semanticModel) | ||
{ | ||
this.semanticModel = semanticModel; | ||
} | ||
|
||
public override SyntaxNode? VisitClassDeclaration(ClassDeclarationSyntax node) | ||
{ | ||
// Ignore any classes that don't implement `IDataProcessor` (consider using semantic model to ensure correct reference) | ||
if (node.BaseList?.Types.Any(t => t.Type.ToString() == "IDataProcessor") == true) | ||
{ | ||
var processDataWrite = node.Members.OfType<MethodDeclarationSyntax>() | ||
.FirstOrDefault(m => m.Identifier.ValueText == "ProcessDataWrite"); | ||
if (processDataWrite is not null) | ||
{ | ||
node = node.ReplaceNode(processDataWrite, Update_DataProcessWrite(processDataWrite)); | ||
} | ||
|
||
var processDataRead = node.Members.OfType<MethodDeclarationSyntax>() | ||
.FirstOrDefault(m => m.Identifier.ValueText == "ProcessDataRead"); | ||
if (processDataRead is not null) | ||
{ | ||
node = node.ReplaceNode(processDataRead, Update_DataProcessRead(processDataRead)); | ||
} | ||
} | ||
|
||
return base.VisitClassDeclaration(node); | ||
} | ||
|
||
private MethodDeclarationSyntax Update_DataProcessRead(MethodDeclarationSyntax processDataRead) | ||
{ | ||
if (processDataRead.ParameterList.Parameters.Count == 3 && | ||
processDataRead.ReturnType.ToString() == "Task<bool>") | ||
{ | ||
processDataRead = ChangeReturnType_FromTaskBool_ToTask(processDataRead); | ||
} | ||
|
||
return processDataRead; | ||
} | ||
|
||
private MethodDeclarationSyntax Update_DataProcessWrite(MethodDeclarationSyntax processDataWrite) | ||
{ | ||
if (processDataWrite.ParameterList.Parameters.Count == 3 && | ||
processDataWrite.ReturnType.ToString() == "Task<bool>") | ||
{ | ||
processDataWrite = AddParameter_ChangedFields(processDataWrite); | ||
processDataWrite = ChangeReturnType_FromTaskBool_ToTask(processDataWrite); | ||
} | ||
|
||
return processDataWrite; | ||
} | ||
|
||
private MethodDeclarationSyntax AddParameter_ChangedFields(MethodDeclarationSyntax method) | ||
{ | ||
return method.ReplaceNode(method.ParameterList, | ||
method.ParameterList.AddParameters(SyntaxFactory.Parameter(SyntaxFactory.Identifier("changedFields")) | ||
.WithLeadingTrivia(SyntaxFactory.Space) | ||
.WithType(SyntaxFactory.ParseTypeName("System.Collections.Generic.Dictionary<string, string?>?")) | ||
.WithLeadingTrivia(SyntaxFactory.Space))); | ||
} | ||
|
||
private MethodDeclarationSyntax ChangeReturnType_FromTaskBool_ToTask(MethodDeclarationSyntax method) | ||
{ | ||
if (method.ReturnType.ToString() == "Task<bool>") | ||
{ | ||
var returnTypeRewriter = new ReturnTypeTaskBooleanRewriter(); | ||
method = (MethodDeclarationSyntax)returnTypeRewriter.Visit(method)!; | ||
} | ||
|
||
return method; | ||
|
||
} | ||
} | ||
|
||
public class ReturnTypeTaskBooleanRewriter : CSharpSyntaxRewriter | ||
{ | ||
public override SyntaxNode? VisitMethodDeclaration(MethodDeclarationSyntax node) | ||
{ | ||
if (node.ReturnType.ToString() == "Task<bool>") | ||
{ | ||
// Change return type | ||
node = node.WithReturnType( | ||
SyntaxFactory.ParseTypeName("Task").WithTrailingTrivia(SyntaxFactory.Space)); | ||
} | ||
return base.VisitMethodDeclaration(node); | ||
} | ||
|
||
public override SyntaxNode? VisitBlock(BlockSyntax node) | ||
{ | ||
foreach (var returnStatementSyntax in node.Statements.OfType<ReturnStatementSyntax>()) | ||
{ | ||
var leadingTrivia = returnStatementSyntax.GetLeadingTrivia(); | ||
var trailingTrivia = returnStatementSyntax.GetTrailingTrivia(); | ||
// When we add multiple lines of code, we need the indentation and a newline | ||
var leadingTriviaMiddle = leadingTrivia.LastOrDefault(t => t.IsKind(SyntaxKind.WhitespaceTrivia)); | ||
var trailingTriviaMiddle = trailingTrivia.FirstOrDefault(t => t.IsKind(SyntaxKind.EndOfLineTrivia)); | ||
// If we don't find a newline, just guess that LF is used. Will likely work anyway. | ||
if (trailingTriviaMiddle == default) trailingTriviaMiddle = SyntaxFactory.LineFeed; | ||
|
||
|
||
switch (returnStatementSyntax.Expression) | ||
{ | ||
// return true/false/variableName | ||
case IdentifierNameSyntax: | ||
case LiteralExpressionSyntax: | ||
case null: | ||
node = node.ReplaceNode(returnStatementSyntax, | ||
SyntaxFactory.ReturnStatement() | ||
.WithLeadingTrivia(leadingTrivia).WithTrailingTrivia(trailingTrivia)); | ||
break; | ||
// case "Task.FromResult(...)": | ||
case InvocationExpressionSyntax | ||
{ | ||
Expression: MemberAccessExpressionSyntax | ||
{ | ||
Expression: IdentifierNameSyntax { Identifier: {Text: "Task" } }, | ||
Name: { Identifier: {Text: "FromResult"}} | ||
}, | ||
ArgumentList: { Arguments: { Count: 1 } } | ||
}: | ||
node = node.ReplaceNode(returnStatementSyntax, | ||
SyntaxFactory.ReturnStatement(SyntaxFactory.ParseExpression(" Task.CompletedTask")) | ||
.WithLeadingTrivia(leadingTrivia).WithTrailingTrivia(trailingTrivia)); | ||
break; | ||
// case "await Task.FromResult(...)": | ||
// Assume we need an await to silence CS1998 and rewrite to | ||
// await Task.CompletedTask; return; | ||
// Could be dropped if we ignore CS1998 | ||
case AwaitExpressionSyntax | ||
{ | ||
Expression: InvocationExpressionSyntax | ||
{ | ||
Expression: MemberAccessExpressionSyntax | ||
{ | ||
Expression: IdentifierNameSyntax { Identifier: {Text: "Task" } }, | ||
Name: { Identifier: {Text: "FromResult"}} | ||
}, | ||
ArgumentList: { Arguments: [{Expression: IdentifierNameSyntax or LiteralExpressionSyntax}]} | ||
} | ||
}: | ||
node = node.WithStatements(node.Statements.ReplaceRange(returnStatementSyntax, new StatementSyntax[] | ||
{ | ||
// Uncomment if cs1998 isn't disabled | ||
// SyntaxFactory.ParseStatement("await Task.CompletedTask;") | ||
// .WithLeadingTrivia(leadingTrivia).WithTrailingTrivia(trailingTriviaMiddle), | ||
|
||
SyntaxFactory.ReturnStatement() | ||
.WithLeadingTrivia(leadingTriviaMiddle).WithTrailingTrivia(trailingTrivia), | ||
|
||
})); | ||
break; | ||
// Just add move the return; statement after the existing return value | ||
default: | ||
node = node.WithStatements(node.Statements.ReplaceRange(returnStatementSyntax, | ||
new StatementSyntax[] | ||
{ | ||
SyntaxFactory.ExpressionStatement(returnStatementSyntax.Expression) | ||
.WithLeadingTrivia(leadingTrivia).WithTrailingTrivia(trailingTriviaMiddle), | ||
|
||
SyntaxFactory.ReturnStatement() | ||
.WithLeadingTrivia(leadingTriviaMiddle).WithTrailingTrivia(trailingTrivia), | ||
})); | ||
break; | ||
} | ||
} | ||
|
||
return base.VisitBlock(node); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.