mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-06-13 22:48:42 +00:00
RPC Test
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
#nullable enable
|
||||
|
||||
using Esiur.Data;
|
||||
using Esiur.Resource;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Esiur.Tests.RPC.EsiurServer
|
||||
{
|
||||
// ====================== Enums ======================
|
||||
|
||||
[Export]
|
||||
public enum Currency
|
||||
{
|
||||
IQD,
|
||||
CNH,
|
||||
USD,
|
||||
EUR,
|
||||
JPY,
|
||||
GBP
|
||||
}
|
||||
|
||||
[Export]
|
||||
public enum DocType
|
||||
{
|
||||
Quote,
|
||||
Order,
|
||||
Invoice,
|
||||
CreditNote
|
||||
}
|
||||
|
||||
[Export]
|
||||
public enum PaymentMethod
|
||||
{
|
||||
Cash,
|
||||
Card,
|
||||
Wire,
|
||||
Crypto,
|
||||
Other
|
||||
}
|
||||
|
||||
[Export]
|
||||
public enum LineType
|
||||
{
|
||||
Product,
|
||||
Service,
|
||||
Discount,
|
||||
Shipping
|
||||
}
|
||||
|
||||
// Variant.Kind
|
||||
[Export]
|
||||
public enum Kind
|
||||
{
|
||||
Null,
|
||||
Bool,
|
||||
Int64,
|
||||
UInt64,
|
||||
Double,
|
||||
Decimal,
|
||||
String,
|
||||
Bytes,
|
||||
DateTime,
|
||||
Guid
|
||||
}
|
||||
|
||||
// ====================== Variant & Entry helpers ======================
|
||||
[Export]
|
||||
public sealed class Variant:IRecord
|
||||
{
|
||||
public Kind Tag { get; set; }
|
||||
|
||||
public bool? Bool { get; set; }
|
||||
public long? I64 { get; set; }
|
||||
public ulong? U64 { get; set; }
|
||||
public double? F64 { get; set; }
|
||||
public string? Str { get; set; }
|
||||
public byte[]? Bytes { get; set; }
|
||||
public DateTime? Dt { get; set; }
|
||||
public byte[]? Guid { get; set; }
|
||||
}
|
||||
|
||||
[Export]
|
||||
public sealed class MetaEntry:IRecord
|
||||
{
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public Variant Value { get; set; } = new Variant();
|
||||
}
|
||||
|
||||
[Export]
|
||||
public sealed class ExtEntry:IRecord
|
||||
{
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public Variant Value { get; set; } = new Variant();
|
||||
}
|
||||
|
||||
// ====================== Party & Address ======================
|
||||
[Export]
|
||||
public sealed class Address:IRecord
|
||||
{
|
||||
public string Line1 { get; set; } = string.Empty;
|
||||
public string? Line2 { get; set; }
|
||||
|
||||
public string City { get; set; } = string.Empty;
|
||||
public string Region { get; set; } = string.Empty;
|
||||
public string Country { get; set; } = string.Empty;
|
||||
public string? PostalCode { get; set; }
|
||||
}
|
||||
|
||||
[Export]
|
||||
public sealed class Party:IRecord
|
||||
{
|
||||
public ulong Id { get; set; }
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? TaxId { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
|
||||
public Address? Address { get; set; }
|
||||
|
||||
public string? PreferredLanguage { get; set; }
|
||||
}
|
||||
|
||||
// ====================== DocumentHeader ======================
|
||||
|
||||
[Export]
|
||||
public sealed class DocumentHeader:IRecord
|
||||
{
|
||||
// Guid serialized as bytes
|
||||
public byte[] DocId { get; set; } = Array.Empty<byte>();
|
||||
|
||||
public DocType Type { get; set; }
|
||||
public int Version { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
public Currency Currency { get; set; }
|
||||
|
||||
public string? Notes { get; set; }
|
||||
|
||||
// corresponds to Dictionary<string, Variant>
|
||||
public Dictionary<string, Variant> Meta { get; set; } = new();
|
||||
}
|
||||
|
||||
// ====================== LineItem, Payment, Attachment ======================
|
||||
|
||||
[Export]
|
||||
public sealed class LineItem:IRecord
|
||||
{
|
||||
public int LineNo { get; set; }
|
||||
public LineType Type { get; set; }
|
||||
|
||||
public string SKU { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public double Qty { get; set; }
|
||||
public string QtyUnit { get; set; } = string.Empty;
|
||||
|
||||
public double UnitPrice { get; set; }
|
||||
|
||||
public double? VatRate { get; set; }
|
||||
public double? Discount { get; set; }
|
||||
|
||||
// Dictionary<string, Variant>
|
||||
public Map<string, Variant> Ext { get; set; } = new();
|
||||
}
|
||||
|
||||
[Export]
|
||||
public sealed class Payment:IRecord
|
||||
{
|
||||
public PaymentMethod Method { get; set; }
|
||||
|
||||
public double Amount { get; set; }
|
||||
public string? Reference { get; set; }
|
||||
|
||||
public DateTime Timestamp { get; set; }
|
||||
|
||||
public double? Fee { get; set; }
|
||||
|
||||
public Currency Currency { get; set; }
|
||||
}
|
||||
|
||||
[Export]
|
||||
public sealed class Attachment:IRecord
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string MimeType { get; set; } = string.Empty;
|
||||
public byte[] Data { get; set; } = Array.Empty<byte>();
|
||||
}
|
||||
|
||||
// ====================== Top-level BusinessDocument ======================
|
||||
[Export]
|
||||
public sealed class BusinessDocument:IRecord
|
||||
{
|
||||
public DocumentHeader Header { get; set; } = new DocumentHeader();
|
||||
|
||||
public Party Seller { get; set; } = new Party();
|
||||
public Party Buyer { get; set; } = new Party();
|
||||
|
||||
public LineItem[] Items { get; set; } = Array.Empty<LineItem>();
|
||||
public Payment[] Payments { get; set; } = Array.Empty<Payment>();
|
||||
public Attachment[] Attachments { get; set; } = Array.Empty<Attachment>();
|
||||
|
||||
public int[] RiskScores { get; set; } = Array.Empty<int>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Libraries\Esiur\Esiur.csproj" OutputItemType="Analyzer" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,33 @@
|
||||
// See https://aka.ms/new-console-template for more information
|
||||
using Esiur.Data;
|
||||
using Esiur.Protocol;
|
||||
using Esiur.Proxy;
|
||||
using Esiur.Resource;
|
||||
using Esiur.Stores;
|
||||
using Esiur.Tests.RPC.EsiurServer;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
ushort port = 5005;
|
||||
|
||||
if (args.Count() > 0)
|
||||
port = ushort.Parse(args[0]);
|
||||
|
||||
Console.WriteLine($"Esiur server listening on port {port}...");
|
||||
|
||||
var wh = Warehouse.Default;
|
||||
var mem = await wh.Put("sys", new MemoryStore());
|
||||
var service = await wh.Put("sys/service", new Service());
|
||||
var ds = await wh.Put("sys/server", new EpServer() { Port = port, EntryPoint = service,
|
||||
AllowUnauthorizedAccess = true });
|
||||
|
||||
await wh.Open();
|
||||
|
||||
|
||||
Console.WriteLine("Open");
|
||||
|
||||
if (!Directory.Exists("template"))
|
||||
Directory.CreateDirectory("template");
|
||||
|
||||
TypeDefGenerator.GetTypes("ep://localhost:5005/sys/service", "template");
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>bin\Release\net10.0\publish\</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<_TargetId>Folder</_TargetId>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,311 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Esiur.Core;
|
||||
using Esiur.Data;
|
||||
using Esiur.Protocol;
|
||||
using Esiur.Resource;
|
||||
|
||||
namespace Esiur.Tests.RPC.EsiurServer
|
||||
{
|
||||
[Resource]
|
||||
public partial class Service : EntryPoint
|
||||
{
|
||||
|
||||
public static bool[] GenerateRandomBoolSequence(
|
||||
int length,
|
||||
double probabilityTrue,
|
||||
Random? rng = null)
|
||||
{
|
||||
if (length <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(length));
|
||||
|
||||
if (probabilityTrue < 0.0 || probabilityTrue > 1.0)
|
||||
throw new ArgumentOutOfRangeException(nameof(probabilityTrue));
|
||||
|
||||
rng ??= Random.Shared;
|
||||
|
||||
int trueTarget = (int)Math.Round(length * probabilityTrue);
|
||||
int remaining = length;
|
||||
int remainingTrue = trueTarget;
|
||||
|
||||
var result = new bool[length];
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
// Probability adjusted to guarantee exact total
|
||||
double p = (double)remainingTrue / remaining;
|
||||
bool value = rng.NextDouble() < p;
|
||||
|
||||
result[i] = value;
|
||||
|
||||
if (value)
|
||||
remainingTrue--;
|
||||
|
||||
remaining--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
public List<TestObject> TestObjects = new List<TestObject>();
|
||||
|
||||
[Export]
|
||||
public event ResourceEventHandler<byte[]> MessageUpdated;
|
||||
|
||||
[Export]
|
||||
|
||||
public byte[] messageToChange;
|
||||
|
||||
[Export]
|
||||
public object testProperty;
|
||||
|
||||
// ---------- Unary: scalars & bytes ----------
|
||||
[Export]
|
||||
public byte[] EchoBytes(byte[] payload)
|
||||
=> payload;
|
||||
[Export]
|
||||
|
||||
public BusinessDocument[] EchoDocuments(BusinessDocument[] payload)
|
||||
=> payload;
|
||||
[Export]
|
||||
|
||||
public int[] EchoIntArray(int[] payload)
|
||||
=> payload;
|
||||
[Export]
|
||||
|
||||
public string[] EchoStringArray(string[] payload)
|
||||
=> payload;
|
||||
[Export]
|
||||
|
||||
public Map<string, BusinessDocument> EchoMap(Map<string, BusinessDocument> payload)
|
||||
=> payload;
|
||||
[Export]
|
||||
|
||||
public DocType[] EchoEnumArray(DocType[] payload)
|
||||
=> payload;
|
||||
|
||||
[Export]
|
||||
|
||||
public AsyncReply<byte[]> ChunkTest(int count, int size, int delay)
|
||||
{
|
||||
var rt = new AsyncReply<byte[]>();
|
||||
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(3000);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
byte[] chunk = new byte[size];
|
||||
new Random().NextBytes(chunk);
|
||||
rt.TriggerChunk(chunk);
|
||||
await Task.Delay(delay);
|
||||
}
|
||||
|
||||
rt.Trigger(new byte[0]);
|
||||
});
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
|
||||
[Export]
|
||||
public async void PropertyChangeTest(int count, int size, int delay)
|
||||
{
|
||||
await Task.Delay(3000);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
byte[] chunk = new byte[size];
|
||||
new Random().NextBytes(chunk);
|
||||
|
||||
MessageToChange = chunk;
|
||||
await Task.Delay(delay);
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public async void EventTest(int count, int size, int delay)
|
||||
{
|
||||
await Task.Delay(3000);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
byte[] chunk = new byte[size];
|
||||
new Random().NextBytes(chunk);
|
||||
|
||||
MessageUpdated?.Invoke(chunk);
|
||||
await Task.Delay(delay);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static Random rand = new Random(322221);
|
||||
|
||||
[Export]
|
||||
public async AsyncReply<TestObject> StartUpdatesLocal(int interval, int count, double localProbability)
|
||||
{
|
||||
|
||||
var dis = GenerateRandomBoolSequence(count, localProbability, new Random(2222));
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
//var probability = rand.NextDouble();
|
||||
|
||||
if (dis[i])// probability <= localProbability)
|
||||
{
|
||||
var o = await Warehouse.Default.New<TestObject>("sys/anything");
|
||||
|
||||
o.Value = i;
|
||||
o.Name = "Update " + i;
|
||||
|
||||
TestObjects.Add(o);
|
||||
|
||||
TestProperty = o;
|
||||
}
|
||||
else
|
||||
{
|
||||
TestProperty = i;
|
||||
}
|
||||
|
||||
await Task.Delay(interval);
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[Export]
|
||||
public async AsyncReply<ResourceLink<TestObject>> StartUpdatesRemote(int interval, int count, double remoteProbability, string remoteLink)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var probability = rand.NextDouble();
|
||||
|
||||
if (probability <= remoteProbability)
|
||||
{
|
||||
TestProperty = remoteLink;
|
||||
}
|
||||
else
|
||||
{
|
||||
TestProperty = i;
|
||||
}
|
||||
|
||||
await Task.Delay(interval);
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
[Export]
|
||||
public async AsyncReply<TestObject> StartUpdatesMirror(int interval, int count, double remoteProbability, string remoteNode, string remoteLink)
|
||||
{
|
||||
|
||||
var remoteCon = await Warehouse.Default.Get<EpConnection>(remoteNode);
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var probability = rand.NextDouble();
|
||||
|
||||
if (probability <= remoteProbability)
|
||||
{
|
||||
var o = await remoteCon.Get(remoteLink);
|
||||
TestObjects.Add(o as TestObject);
|
||||
|
||||
TestProperty = o;
|
||||
}
|
||||
else
|
||||
{
|
||||
TestProperty = i;
|
||||
}
|
||||
|
||||
await Task.Delay(interval);
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Export]
|
||||
public async AsyncReply<ResourceLink<TestObject>> StartUpdates(int interval, int count, double localProbability, double remoteProbability, string remoteHostLink)
|
||||
{
|
||||
|
||||
//var created = new List<TestObject>();
|
||||
|
||||
//for (var i = 0; i < count; i++)
|
||||
//{
|
||||
// var o = await Warehouse.Default.New<TestObject>("sys/anything");
|
||||
// o.Value = i;
|
||||
// o.Name = "Update " + i;
|
||||
// created.Add(o);
|
||||
//}
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
|
||||
//TestProperty = created[rand.Next(999)];
|
||||
//await Task.Delay(interval);
|
||||
|
||||
// Console.WriteLine("Updating " + i);
|
||||
var probability = rand.NextDouble();
|
||||
|
||||
if ((localProbability != 0 && probability <= localProbability) || localProbability == 1)
|
||||
{
|
||||
var o = await Warehouse.Default.New<TestObject>("sys/anything");
|
||||
|
||||
o.Value = i;
|
||||
o.Name = "Update " + i;
|
||||
|
||||
TestObjects.Add(o);
|
||||
|
||||
TestProperty = o;
|
||||
}
|
||||
else if (probability < localProbability + remoteProbability)
|
||||
{
|
||||
TestProperty = new ResourceLink(remoteHostLink);
|
||||
}
|
||||
else
|
||||
{
|
||||
TestProperty = i;
|
||||
}
|
||||
|
||||
await Task.Delay(interval);
|
||||
}
|
||||
|
||||
TestObjects.Clear();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override async AsyncReply<IResource> Query(string path, EpConnection sender)
|
||||
{
|
||||
if (path == "gen")
|
||||
{
|
||||
var o = await Warehouse.Default.New<TestObject>("sys/anything");
|
||||
o.Value = rand.Next();
|
||||
o.Name = "Update " + o.Value;
|
||||
TestObjects.Add(o);
|
||||
return o;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.Instance != null)
|
||||
return await this.Instance.Warehouse.Query(path);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool Create()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Esiur.Resource;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Tests.RPC.EsiurServer
|
||||
{
|
||||
[Resource]
|
||||
public partial class TestObject
|
||||
{
|
||||
[Export] int size;
|
||||
[Export] string name;
|
||||
[Export] object value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user