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

Mock Data By reflection all Dataset , Locale and methods are shown #3838

Merged
merged 6 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
Expand Up @@ -21,6 +21,7 @@ limitations under the License.
using Amdocs.Ginger.Plugin.Core;
using Ginger.Actions;
using Ginger.UserControlsLib.TextEditor.Common;
using GingerCore;
using GingerCore.Variables;
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Highlighting;
Expand Down Expand Up @@ -193,6 +194,15 @@ private Page GetPageFor(SelectedContentArgs SelectedContentArgs)
}
}

if(txt.StartsWith("{MockDataExp Fun="))
{
Mockdata expParams = GingerCore.ValueExpression.GetMockDataDatasetsFunction(txt);
if (expParams != null)
{
p = new ValueExpressionMockDataEditorPage(mContext, SelectedContentArgs,expParams.MockDataDatasets, expParams.Function, expParams.Locale, expParams.MockExpression);
}
}
Comment on lines +197 to +204
Copy link
Contributor

@coderabbitai coderabbitai bot Jul 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New Logic to Handle {MockDataExp Fun= Expressions

The added logic to handle expressions starting with {MockDataExp Fun= is well-integrated within the GetPageFor method. This method now checks for the specific pattern and, if matched, uses GingerCore.ValueExpression.GetMockDataDatasetsFunction to parse the expression and create a new instance of ValueExpressionMockDataEditorPage.

  • Correctness: The implementation correctly checks for null values before proceeding with the creation of the page, which prevents potential null reference exceptions.
  • Performance: Ensure that the GetMockDataDatasetsFunction method is optimized for performance, especially if it involves complex parsing or external data fetching.

Overall, the implementation follows good practices, but it's recommended to add unit tests to cover these new scenarios to ensure stability and correctness.

Would you like me to help in writing unit tests for this new functionality?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes please

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@prashelke, here are the unit tests for the new functionality handling {MockDataExp Fun= expressions in the GetPageFor method. These tests will help ensure that the method behaves as expected under various scenarios:

using NUnit.Framework;
using Ginger.UserControlsLib.TextEditor.ValueExpression;
using GingerCoreNET.ValueExpressionLib;
using amdocs.ginger.GingerCoreNET;
using Amdocs.Ginger.Common;

namespace GingerCoreNETUnitTest.ValueExpressionTests
{
    [TestFixture]
    public class ValueExpressionEditorTests
    {
        private ValueExpressionEditor editor;
        private Context context;

        [SetUp]
        public void Setup()
        {
            context = new Context();
            editor = new ValueExpressionEditor(context);
        }

        [Test]
        public void GetPageFor_ValidMockDataExp_ReturnsMockDataEditorPage()
        {
            // Arrange
            string expression = "{MockDataExp Fun=ValidFunction}";
            SelectedContentArgs args = new SelectedContentArgs { TextEditor = new MockTextEditor(expression) };

            // Act
            Page result = editor.GetPageFor(args);

            // Assert
            Assert.IsNotNull(result);
            Assert.IsInstanceOf<ValueExpressionMockDataEditorPage>(result);
        }

        [Test]
        public void GetPageFor_InvalidMockDataExp_ReturnsNull()
        {
            // Arrange
            string expression = "{MockDataExp Fun=}";
            SelectedContentArgs args = new SelectedContentArgs { TextEditor = new MockTextEditor(expression) };

            // Act
            Page result = editor.GetPageFor(args);

            // Assert
            Assert.IsNull(result);
        }

        [Test]
        public void GetPageFor_NullInput_ThrowsNoException()
        {
            // Arrange
            SelectedContentArgs args = null;

            // Act & Assert
            Assert.DoesNotThrow(() => editor.GetPageFor(args));
        }
    }
    
    // Mock implementation of TextEditor to simulate user input
    public class MockTextEditor : ITextEditor
    {
        public string Text { get; set; }

        public MockTextEditor(string text)
        {
            Text = text;
        }
    }
}

These tests check for valid input, invalid input, and null input scenarios. Feel free to adjust the test cases or add more to cover additional cases you might be concerned about.


if (txt.StartsWith("{EnvURL App="))
{
//TODO: impl get page for Env
Expand Down Expand Up @@ -221,17 +231,28 @@ private Page GetPageFor(SelectedContentArgs SelectedContentArgs)

public override void UpdateSelectedContent()
{
if (p.GetType() == typeof(ValueExpressionVariableEditorPage))
{
((ValueExpressionVariableEditorPage)p).UpdateContent();
}
else if (p.GetType() == typeof(ActDataSourcePage))
try
{
((ActDataSourcePage)p).UpdateContent();
if (p.GetType() == typeof(ValueExpressionVariableEditorPage))
{
((ValueExpressionVariableEditorPage)p).UpdateContent();
}
else if (p.GetType() == typeof(ActDataSourcePage))
{
((ActDataSourcePage)p).UpdateContent();
}
else if (p.GetType() == typeof(ValueExpressionFlowDetailsEditorPage))
{
((ValueExpressionFlowDetailsEditorPage)p).UpdateContent();
}
else if (p.GetType() == typeof(ValueExpressionMockDataEditorPage))
{
((ValueExpressionMockDataEditorPage)p).UpdateContent();
}
}
else if (p.GetType() == typeof(ValueExpressionFlowDetailsEditorPage))
catch (Exception ex)
{
((ValueExpressionFlowDetailsEditorPage)p).UpdateContent();
Reporter.ToLog(eLogLevel.ERROR, "Failed to update the selected content", ex);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Page x:Class="Ginger.UserControlsLib.TextEditor.ValueExpression.ValueExpressionMockDataEditorPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Ginger.UserControlsLib.TextEditor.ValueExpression"
xmlns:GingerCore="clr-namespace:GingerCore;assembly=GingerCore"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="ValueExpressionMockDataEditorPage">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="15*"/>
</Grid.RowDefinitions>

<Label x:Name="lbl" Grid.Row="0" Style="{StaticResource $LabelStyle}">
<GingerCore:ucTextDicResource Text="Select Alternative Functions:"/>
</Label>
<ListView x:Name="FunctionsList" Grid.Row="1" Style="{StaticResource $ListBoxStyle}" Foreground="{StaticResource $SelectionColor_Pink}" BorderThickness="0">
prashelke marked this conversation as resolved.
Show resolved Hide resolved

</ListView>
</Grid>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
#region License
/*
Copyright © 2014-2024 European Support Limited

Licensed under the Apache License, Version 2.0 (the "License")
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion

using Amdocs.Ginger.Common;
using Bogus;
using Ginger.UserControlsLib.TextEditor.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows.Controls;
using GingerCore;
using Org.BouncyCastle.Asn1.Pkcs;

namespace Ginger.UserControlsLib.TextEditor.ValueExpression
{
/// <summary>
/// Interaction logic for ValueExpressionMockDataEditorPage.xaml
/// </summary>
public partial class ValueExpressionMockDataEditorPage : Page
{
SelectedContentArgs mSelectedContentArgs;
Context mContext;
string mObj;
static List<string> Localelst;
static List<string> objClasses;
public ValueExpressionMockDataEditorPage(Context context, SelectedContentArgs SelectedContentArgs, string obj, string function, string Locale, string MockExpression)
{
try
{


InitializeComponent();
mContext = context;
mSelectedContentArgs = SelectedContentArgs;
mObj = obj;
int datasetStartindex = MockExpression.IndexOf('=');
int datasetendindex = MockExpression.IndexOf('(');
int localestartindex = MockExpression.IndexOf('"');
int localeendindex = MockExpression.IndexOf(')');
int functionstartindex = MockExpression.IndexOf('.');

Localelst = Localelst == null ? GetLocales() : Localelst;

Check warning on line 58 in Ginger/Ginger/UserControlsLib/TextEditor/ValueExpression/ValueExpressionMockDataEditorPage.xaml.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

Ginger/Ginger/UserControlsLib/TextEditor/ValueExpression/ValueExpressionMockDataEditorPage.xaml.cs#L58

Remove this assignment of 'Localelst' or initialize it statically.
Type objType;
List<string> methodList = new List<string>();

Check warning on line 60 in Ginger/Ginger/UserControlsLib/TextEditor/ValueExpression/ValueExpressionMockDataEditorPage.xaml.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

Ginger/Ginger/UserControlsLib/TextEditor/ValueExpression/ValueExpressionMockDataEditorPage.xaml.cs#L60

Remove this useless assignment to local variable 'methodList'.

Assembly assembly = Assembly.Load("Bogus"); // or load your target assembly

string namespaceName = "Bogus.DataSets";

objClasses = objClasses == null ? GetObjectClasses(assembly, namespaceName) : objClasses;

Check warning on line 66 in Ginger/Ginger/UserControlsLib/TextEditor/ValueExpression/ValueExpressionMockDataEditorPage.xaml.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

Ginger/Ginger/UserControlsLib/TextEditor/ValueExpression/ValueExpressionMockDataEditorPage.xaml.cs#L66

Remove this assignment of 'objClasses' or initialize it statically.

string objTypeName = mObj.Equals("Randomizer") ? $"Bogus.{mObj}" : $"Bogus.DataSets.{mObj}";
objType = assembly.GetType(objTypeName);

methodList = GetMethodsOfType(objType);
int CaretPossition = SelectedContentArgs.GetCaretPosition();
if ((datasetStartindex < CaretPossition) && (CaretPossition < datasetendindex))
{
FunctionsList.ItemsSource = objClasses.OrderBy(x => x).ToList();
}
else if ((localestartindex < CaretPossition) && (CaretPossition < localeendindex) && (!mObj.Equals("Randomizer") || !mObj.Equals("Finance")))
{
FunctionsList.ItemsSource = Localelst.OrderBy(x => x).ToList();
}
else
{
FunctionsList.ItemsSource = methodList.OrderBy(x => x).ToList();
}
}
catch (Exception ex)
{
Reporter.ToLog(eLogLevel.ERROR, "Failed to load ValueExpressionMockDataEditorPage", ex);
}
}


private static List<string> GetLocales()
{
// Replace with actual implementation to fetch locales
return Bogus.Database.GetAllLocales().ToList();
prashelke marked this conversation as resolved.
Show resolved Hide resolved
}

private static List<string> GetObjectClasses(Assembly assembly, string namespaceName)
{
Type[] types = assembly.GetTypes();
List<string> objclass = types.Where(t => t.Namespace == namespaceName && typeof(DataSet).IsAssignableFrom(t) && t != typeof(DataSet) && t.FullName.Contains(namespaceName))
.Select(x => x.Name)
.ToList();
objclass.Add("Randomizer");
return objclass;
}
Comment on lines +96 to +104
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of GetObjectClasses method

This method fetches classes from the Bogus.DataSets namespace that are assignable from DataSet. It's a good use of reflection for dynamic type discovery.

  • Performance Optimization: Consider caching the result of assembly.GetTypes() if this method is called frequently.
  • Error Handling: Add error handling for type loading and filtering to prevent runtime exceptions if the assembly or types are not available.


private static List<string> GetMethodsOfType(Type objType)
{
List<string> methodList = new List<string>();

if (objType != null)
{
MethodInfo[] methods = objType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);

if (methods != null)
{
var excludedMethods = GetExcludedMethods(objType.ToString());
var groupedMethods = methods.GroupBy(m => m.Name);
foreach (var methodGroup in groupedMethods)
{
foreach (MethodInfo method in methodGroup)
{
if (excludedMethods.Contains(method.Name))
{
continue;
}
string parameters = string.Join(", ", method.GetParameters().Select(p => FormatParameter(p)));

string functionString = $"{method.Name}({parameters})";
methodList.Add(functionString);
}
}
}
}

return methodList;
}
Comment on lines +106 to +136
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of GetMethodsOfType method

This method dynamically fetches and formats methods of a given type. It excludes certain methods based on the type.

  • Complexity and Maintainability: The method is complex and does multiple things: fetching methods, filtering them, and formatting. Consider breaking this method into smaller, more focused methods.
  • Error Handling: There is no handling for null objType, which could lead to NullReferenceException at runtime.


private static HashSet<string> GetExcludedMethods(string objType)
{
return objType switch
{
"Bogus.Randomizer" => new HashSet<string> { "EnumValues", "WeightedRandom", "ArrayElement", "ArrayElements", "ListItem", "ListItems", "ReplaceSymbols" },
"Bogus.DataSets.Date" => new HashSet<string> { "BetweenTimeOnly" },
_ => new HashSet<string>()
};
}

private static string FormatParameter(ParameterInfo parameter)
{
string paramType = parameter.ParameterType.Name;
string paramName = parameter.Name;
bool hasDefaultValue = parameter.HasDefaultValue;
object defaultValue = hasDefaultValue ? $"{FormatDefaultValue(parameter.DefaultValue)}" : SetDefaultValue(parameter);
return $"{defaultValue}";

}

private static object SetDefaultValue(ParameterInfo parameter)
{
Type parameterType = parameter.ParameterType;
prashelke marked this conversation as resolved.
Show resolved Hide resolved

return parameterType switch
{
Type type when type == typeof(DateTime) && parameter.Name.Equals("start") => "Past(1)",
Type type when type == typeof(DateTime) && parameter.Name.Equals("end") => "Future(1)",

Type type when type == typeof(DateOnly) && parameter.Name.Equals("start") => "PastDateOnly(1)",
Type type when type == typeof(DateOnly) && parameter.Name.Equals("end") => "FutureDateOnly(1)",

Type type when type == typeof(DateTimeOffset) && parameter.Name.Equals("start") => "PastOffset(1)",
Type type when type == typeof(DateTimeOffset) && parameter.Name.Equals("end") => "FutureOffset(1)",

Type type when type == typeof(Array) => "[1,2,3,4]",

Type type when type == typeof(List<int>) => "[1,2,3,4,5]",

Type type when type == typeof(string) => parameter.Name switch
{
"format" => "'12#34'",
"symbol" => "'#'",
_ => "'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"
},

_ => GetDefaultForType(parameterType)
};
}

public static T[] CreateArrayWithDefaultValues<T>(int length, T defaultValue)
{
T[] array = new T[length];
for (int i = 0; i < length; i++)
{
array[i] = defaultValue;
}
return array;
}

private static object GetDefaultForType(Type type)
{
return type.IsValueType ? Activator.CreateInstance(type) : null;
}

private static string FormatDefaultValue(object defaultValue)
{
switch (defaultValue)
{
case null:
return "null";
case string s:
return $"\"{s}\"";
case char c:
return $"'{c}'";
case bool b:
return b.ToString().ToLower();
default:
return defaultValue.ToString();
}
}
public void UpdateContent()
{
try
{
string selectedItem = (string)FunctionsList.SelectedItem;
string initialText = mSelectedContentArgs.TextEditor.Text.Substring(0, mSelectedContentArgs.StartPos);

// Parsing the text editor content
string objStr, functions, Locale;
Mockdata expParams = GingerCore.ValueExpression.GetMockDataDatasetsFunction(mSelectedContentArgs.TextEditor.Text);

string editorText = mSelectedContentArgs.TextEditor.Text;

int caretPosition = mSelectedContentArgs.GetCaretPosition();
string resultText = BuildResultText(selectedItem, expParams.MockDataDatasets, expParams.Locale, expParams.Function, caretPosition, expParams.DatasetStartIndex, expParams.DatasetEndIndex, expParams.LocaleStartIndex, expParams.LocaleEndIndex);

// Append the remaining text and update the text editor content
resultText += editorText.Substring(mSelectedContentArgs.EndPos + 1);
mSelectedContentArgs.TextEditor.Text = resultText;
}
catch(Exception ex)
{
Reporter.ToLog(eLogLevel.ERROR, "Failed to update content in ValueExpressionMockDataEditorPage", ex);
}
}

static string ExtractSubstring(string text, int startIndex, int length)
{
return text.Substring(startIndex, length);
}

static string BuildResultText(string selectedItem, string objStr, string locale, string functions, int caretPosition, int datasetStartIndex, int datasetEndIndex, int localeStartIndex, int localeEndIndex)
{
string resultText = string.Empty;
bool isWithinDataset = datasetStartIndex < caretPosition && caretPosition < datasetEndIndex;
bool isWithinLocale = localeStartIndex < caretPosition && caretPosition < localeEndIndex;

if (objStr.Equals("Randomizer") || objStr.Equals("Finance"))
{
resultText += isWithinDataset
? GenerateResultText(selectedItem, "Bogus", isWithinDataset)
: "{MockDataExp Fun=" + objStr + "()." + selectedItem + ";}";
}
else
{
if (isWithinDataset)
{
resultText += GenerateResultText(selectedItem, "Bogus", isWithinDataset, locale);
}
else if (isWithinLocale)
{
resultText += "{MockDataExp Fun=" + objStr + "(@\"" + selectedItem + "\")." + functions + "();}";
}
else
{
resultText += "{MockDataExp Fun=" + objStr + "(@\"" + locale + "\")." + selectedItem + ";}";
}
}

return resultText;
}

private static string GenerateResultText(string selectedItem, string baseNamespace, bool isWithinDataset, string locale = null)
{
Assembly assembly = Assembly.Load("Bogus");
string objTypeName = selectedItem.Equals("Randomizer") ? $"{baseNamespace}.{selectedItem}" : $"{baseNamespace}.DataSets.{selectedItem}";
Type objType = assembly.GetType(objTypeName);

List<string> methodList = GetMethodsOfType(objType);
string methodCall = methodList.FirstOrDefault();

if (locale == null)
{
return "{MockDataExp Fun=" + selectedItem + "()." + methodCall + ";}";
}
else
{
return "{MockDataExp Fun=" + selectedItem + "(@\"" + locale + "\")." + methodCall + ";}";
}
}
}
}
Loading
Loading