Skip to content

Commit f6e600f

Browse files
test: Added tests for Apply to each
#18
1 parent 3a28fa5 commit f6e600f

File tree

4 files changed

+285
-1
lines changed

4 files changed

+285
-1
lines changed

PowerAutomateMockUp/ExpressionParser/ValueContainer.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ public ValueContainer(float floatValue)
6363
_type = ValueType.Float;
6464
}
6565

66+
public ValueContainer(double floatValue)
67+
{
68+
_value = floatValue;
69+
_type = ValueType.Float;
70+
}
71+
6672
public ValueContainer(int intValue)
6773
{
6874
_value = intValue;
@@ -211,12 +217,13 @@ public Dictionary<string, ValueContainer> AsDict()
211217
{
212218
return GetValue<Dictionary<string, ValueContainer>>();
213219
}
220+
214221
throw new PowerAutomateMockUpException("Can't get none object value container as dict.");
215222
}
216223

217224
private ValueContainer JsonToValueContainer(JToken json)
218225
{
219-
if (json.GetType() == typeof(JObject))
226+
if (json is JObject jObject)
220227
{
221228
var dictionary = json.ToDictionary(pair => ((JProperty) pair).Name, token =>
222229
{
@@ -236,9 +243,49 @@ private ValueContainer JsonToValueContainer(JToken json)
236243
return new ValueContainer(dictionary);
237244
}
238245

246+
if (json is JArray jArray)
247+
{
248+
return jArray.Count > 0 ? new ValueContainer() : JArrayToValueContainer(jArray);
249+
}
250+
239251
throw new Exception();
240252
}
241253

254+
private ValueContainer JArrayToValueContainer(JArray json)
255+
{
256+
var list = new List<ValueContainer>();
257+
258+
foreach (var jToken in json)
259+
{
260+
if (jToken.GetType() != typeof(JValue))
261+
{
262+
throw new PowerAutomateMockUpException("Json can only contain arrays of primitive types.");
263+
}
264+
265+
var t = (JValue) jToken;
266+
switch (t.Value)
267+
{
268+
case int i:
269+
list.Add(new ValueContainer(i));
270+
break;
271+
case string s:
272+
list.Add(new ValueContainer(s));
273+
break;
274+
case bool b:
275+
list.Add(new ValueContainer(b));
276+
break;
277+
case double d:
278+
list.Add(new ValueContainer(d));
279+
break;
280+
default:
281+
throw new PowerAutomateMockUpException(
282+
$"Type {t.Value.GetType()} is not recognized when converting Json to ValueContainer.");
283+
}
284+
}
285+
286+
return new ValueContainer(list);
287+
}
288+
242289

243290
public override string ToString()
244291
{
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
{
2+
"name": "3153d4d3-ea76-4bf8-9fd0-20dba68dd98f",
3+
"id": "/providers/Microsoft.Flow/flows/3153d4d3-ea76-4bf8-9fd0-20dba68dd98f",
4+
"type": "Microsoft.Flow/flows",
5+
"properties": {
6+
"apiId": "/providers/Microsoft.PowerApps/apis/shared_logicflows",
7+
"displayName": "Button -> List records,Apply to each,Update a record",
8+
"definition": {
9+
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
10+
"contentVersion": "1.0.0.0",
11+
"parameters": {
12+
"$connections": {
13+
"defaultValue": {},
14+
"type": "Object"
15+
},
16+
"$authentication": {
17+
"defaultValue": {},
18+
"type": "SecureObject"
19+
}
20+
},
21+
"triggers": {
22+
"manual": {
23+
"type": "Request",
24+
"kind": "Button",
25+
"inputs": {
26+
"schema": {
27+
"type": "object",
28+
"properties": {},
29+
"required": []
30+
}
31+
}
32+
}
33+
},
34+
"actions": {
35+
"List_records": {
36+
"runAfter": {},
37+
"type": "OpenApiConnection",
38+
"inputs": {
39+
"host": {
40+
"connectionName": "shared_commondataserviceforapps",
41+
"operationId": "ListRecords",
42+
"apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
43+
},
44+
"parameters": {
45+
"entityName": "accounts",
46+
"$filter": "contains(name, 'Test')"
47+
},
48+
"authentication": {
49+
"type": "Raw",
50+
"value": "@json(decodeBase64(triggerOutputs().headers['X-MS-APIM-Tokens']))['$ConnectionKey']"
51+
}
52+
}
53+
},
54+
"Apply_to_each": {
55+
"foreach": "@outputs('List_records')?['body/value']",
56+
"actions": {
57+
"Update_a_record": {
58+
"runAfter": {},
59+
"type": "OpenApiConnection",
60+
"inputs": {
61+
"host": {
62+
"connectionName": "shared_commondataserviceforapps",
63+
"operationId": "UpdateRecord",
64+
"apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
65+
},
66+
"parameters": {
67+
"entityName": "accounts",
68+
"recordId": "@items('Apply_to_each')?['accountid']"
69+
},
70+
"authentication": {
71+
"type": "Raw",
72+
"value": "@json(decodeBase64(triggerOutputs().headers['X-MS-APIM-Tokens']))['$ConnectionKey']"
73+
}
74+
}
75+
}
76+
},
77+
"runAfter": {
78+
"List_records": [
79+
"Succeeded"
80+
]
81+
},
82+
"type": "Foreach"
83+
}
84+
},
85+
"outputs": {}
86+
},
87+
"connectionReferences": {
88+
"shared_commondataserviceforapps": {
89+
"source": "Embedded",
90+
"id": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps",
91+
"tier": "NotSpecified"
92+
}
93+
},
94+
"flowFailureAlertSubscribed": false
95+
}
96+
}

Test/PAMUApplyToEachTest.cs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using NUnit.Framework;
7+
using Parser;
8+
using Parser.ExpressionParser;
9+
using Parser.ExpressionParser.Functions.Base;
10+
using Parser.FlowParser;
11+
using Parser.FlowParser.ActionExecutors;
12+
13+
namespace Test
14+
{
15+
[TestFixture]
16+
public class PAMUApplyToEachTest
17+
{
18+
private static readonly string TestFlowPath = System.IO.Path.GetFullPath(@"FlowSamples");
19+
20+
[Test]
21+
public async Task TestForEachFlow()
22+
{
23+
var path = @$"{TestFlowPath}\PAMUApplyToEach.json";
24+
25+
var services = new ServiceCollection();
26+
27+
services.Configure<FlowSettings>(x => x.FailOnUnknownAction = true);
28+
29+
services.AddFlowActionByName<ManualTrigger>(ManualTrigger.TriggerName);
30+
services.AddFlowActionByName<ListRecordsAccounts>(ListRecordsAccounts.FlowActionName);
31+
services.AddFlowActionByName<UpdateRecord>(UpdateRecord.FlowActionName);
32+
33+
services.AddFlowRunner();
34+
35+
var sp = services.BuildServiceProvider();
36+
var flowRunner = sp.GetRequiredService<FlowRunner>();
37+
38+
flowRunner.InitializeFlowRunner(path);
39+
40+
await flowRunner.Trigger();
41+
42+
var state = sp.GetRequiredService<IState>();
43+
44+
/*
45+
Assert.IsTrue(state.GetOutputs("SecondOutput").Type() != ValueContainer.ValueType.Null);
46+
Assert.IsTrue(state.GetOutputs("ThirdOutput").Type() != ValueContainer.ValueType.Null);
47+
Assert.IsTrue(state.GetOutputs("FourthOutput").Type() == ValueContainer.ValueType.Null);
48+
49+
Assert.IsTrue(state.GetOutputs("SecondOutput").GetValue<bool>(), "Second action wasn't triggered");
50+
Assert.IsTrue(state.GetOutputs("ThirdOutput").GetValue<bool>(), "Third action wasn't triggered");
51+
*/
52+
}
53+
54+
private class ManualTrigger : DefaultBaseActionExecutor
55+
{
56+
public const string TriggerName = "manual";
57+
58+
public override Task<ActionResult> Execute()
59+
{
60+
return Task.FromResult(new ActionResult());
61+
}
62+
}
63+
64+
private class ListRecordsAccounts : OpenApiConnectionActionExecutorBase
65+
{
66+
private readonly IState _state;
67+
public const string FlowActionName = "List_records";
68+
69+
public static readonly Guid[] Guids = {Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid()};
70+
71+
public override Task<ActionResult> Execute()
72+
{
73+
var accountList = new ValueContainer(new[]
74+
{
75+
new ValueContainer(
76+
new Dictionary<string, ValueContainer>
77+
{
78+
{"accountid", new ValueContainer(Guids[0].ToString())}
79+
}),
80+
new ValueContainer(
81+
new Dictionary<string, ValueContainer>
82+
{
83+
{"accountid", new ValueContainer(Guids[1].ToString())}
84+
}),
85+
new ValueContainer(
86+
new Dictionary<string, ValueContainer>
87+
{
88+
{"accountid", new ValueContainer(Guids[2].ToString())}
89+
})
90+
});
91+
92+
_state.AddOutputs("List_records", new ValueContainer(new Dictionary<string, ValueContainer>
93+
{
94+
{"body/value", accountList}
95+
}));
96+
97+
98+
return Task.FromResult(new ActionResult {ActionStatus = ActionStatus.Succeeded});
99+
}
100+
101+
public ListRecordsAccounts(IExpressionEngine expressionEngine, IState state) : base(expressionEngine)
102+
{
103+
_state = state ?? throw new ArgumentNullException(nameof(state));
104+
}
105+
}
106+
107+
108+
private class UpdateRecord : OpenApiConnectionActionExecutorBase
109+
{
110+
private readonly IState _state;
111+
public const string FlowActionName = "Update_a_record";
112+
private static readonly List<string> ProcessedGuids = new List<string>();
113+
114+
public UpdateRecord(IExpressionEngine expressionEngine, IState state) : base(expressionEngine)
115+
{
116+
_state = state ?? throw new ArgumentNullException(nameof(state));
117+
}
118+
119+
public override Task<ActionResult> Execute()
120+
{
121+
var paras = Parameters;
122+
123+
var recordId = paras["recordId"].GetValue<string>();
124+
Assert.IsTrue(
125+
ListRecordsAccounts.Guids.Any(x => x.ToString().Equals(recordId)),
126+
"Record ID from flow action parameters does not match expected.");
127+
128+
Assert.IsTrue(
129+
!ProcessedGuids.Any(x => x.ToString().Equals(recordId)),
130+
"Record ID from flow action parameters is already processed.");
131+
132+
ProcessedGuids.Add(recordId);
133+
134+
return Task.FromResult(new ActionResult());
135+
}
136+
}
137+
}
138+
}

Test/Test.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
<None Update="FlowSamples\Pure CDS ce.json">
3535
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
3636
</None>
37+
<None Update="FlowSamples\PAMUApplyToEach.json">
38+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
39+
</None>
3740
</ItemGroup>
3841

3942
</Project>

0 commit comments

Comments
 (0)