From 2d3dbb0449beb82c16659ea28f0c97a993a70f42 Mon Sep 17 00:00:00 2001 From: stas Date: Thu, 8 Sep 2022 08:11:01 -0700 Subject: [PATCH] tests and bug fixes --- src/ApiService/ApiService/Functions/Node.cs | 6 +- src/ApiService/ApiService/Functions/Tasks.cs | 2 +- .../onefuzzlib/ScalesetOperations.cs | 2 +- .../ApiService/onefuzzlib/VmssOperations.cs | 2 +- .../FunctionalTests/1f-api/ApiBase.cs | 147 ++++++++++- .../FunctionalTests/1f-api/Authentication.cs | 18 ++ .../FunctionalTests/1f-api/Container.cs | 48 ++++ .../FunctionalTests/1f-api/Download.cs | 21 ++ .../FunctionalTests/1f-api/Error.cs | 12 +- src/ApiService/FunctionalTests/1f-api/Info.cs | 48 ++++ src/ApiService/FunctionalTests/1f-api/Jobs.cs | 78 ++++++ .../FunctionalTests/1f-api/Negotiate.cs | 4 + src/ApiService/FunctionalTests/1f-api/Node.cs | 55 ++-- .../FunctionalTests/1f-api/NodeAddSshKey.cs | 21 ++ .../FunctionalTests/1f-api/Notifications.cs | 55 ++++ src/ApiService/FunctionalTests/1f-api/Pool.cs | 39 ++- .../FunctionalTests/1f-api/Proxy.cs | 51 ++-- .../FunctionalTests/1f-api/ReproVmss.cs | 83 ++++++ .../FunctionalTests/1f-api/Scaleset.cs | 104 ++++---- .../FunctionalTests/1f-api/Tasks.cs | 239 ++++++++++++++++++ .../FunctionalTests/1f-api/UserInfo.cs | 16 ++ .../FunctionalTests/1f-api/WebhookLogs.cs | 4 + .../FunctionalTests/1f-api/WebhookPing.cs | 4 + .../FunctionalTests/1f-api/Webhooks.cs | 4 + .../FunctionalTests/FunctionalTests.csproj | 1 + .../FunctionalTests/{1f-api => }/Helpers.cs | 1 - .../FunctionalTests/TestContainer.cs | 55 ++++ src/ApiService/FunctionalTests/TestInfo.cs | 23 ++ src/ApiService/FunctionalTests/TestNode.cs | 28 +- src/ApiService/FunctionalTests/TestPool.cs | 15 +- src/ApiService/FunctionalTests/TestProxy.cs | 31 ++- .../FunctionalTests/TestScaleset.cs | 33 +-- src/ApiService/FunctionalTests/TestTasks.cs | 32 +++ .../FunctionalTests/packages.lock.json | 22 ++ .../IntegrationTests/IntegrationTests.csproj | 1 + .../IntegrationTests/packages.lock.json | 83 +++--- src/ApiService/Tests/packages.lock.json | 66 ++--- 37 files changed, 1182 insertions(+), 272 deletions(-) create mode 100644 src/ApiService/FunctionalTests/1f-api/Authentication.cs create mode 100644 src/ApiService/FunctionalTests/1f-api/Container.cs create mode 100644 src/ApiService/FunctionalTests/1f-api/Download.cs create mode 100644 src/ApiService/FunctionalTests/1f-api/Info.cs create mode 100644 src/ApiService/FunctionalTests/1f-api/Jobs.cs create mode 100644 src/ApiService/FunctionalTests/1f-api/Negotiate.cs create mode 100644 src/ApiService/FunctionalTests/1f-api/NodeAddSshKey.cs create mode 100644 src/ApiService/FunctionalTests/1f-api/Notifications.cs create mode 100644 src/ApiService/FunctionalTests/1f-api/ReproVmss.cs create mode 100644 src/ApiService/FunctionalTests/1f-api/Tasks.cs create mode 100644 src/ApiService/FunctionalTests/1f-api/UserInfo.cs create mode 100644 src/ApiService/FunctionalTests/1f-api/WebhookLogs.cs create mode 100644 src/ApiService/FunctionalTests/1f-api/WebhookPing.cs create mode 100644 src/ApiService/FunctionalTests/1f-api/Webhooks.cs rename src/ApiService/FunctionalTests/{1f-api => }/Helpers.cs (99%) create mode 100644 src/ApiService/FunctionalTests/TestContainer.cs create mode 100644 src/ApiService/FunctionalTests/TestInfo.cs create mode 100644 src/ApiService/FunctionalTests/TestTasks.cs diff --git a/src/ApiService/ApiService/Functions/Node.cs b/src/ApiService/ApiService/Functions/Node.cs index ae4e329b10..21867367e8 100644 --- a/src/ApiService/ApiService/Functions/Node.cs +++ b/src/ApiService/ApiService/Functions/Node.cs @@ -40,7 +40,7 @@ private async Async.Task Get(HttpRequestData req) { req, new Error( Code: ErrorCode.UNABLE_TO_FIND, - Errors: new string[] { "unable to find node " }), + Errors: new string[] { "unable to find node" }), context: machineId.ToString()); } @@ -96,7 +96,7 @@ private async Async.Task Patch(HttpRequestData req) { req, new Error( Code: ErrorCode.UNABLE_TO_FIND, - Errors: new string[] { "unable to find node " }), + Errors: new string[] { "unable to find node" }), context: patch.MachineId.ToString()); } @@ -129,7 +129,7 @@ private async Async.Task Post(HttpRequestData req) { req, new Error( Code: ErrorCode.UNABLE_TO_FIND, - Errors: new string[] { "unable to find node " }), + Errors: new string[] { "unable to find node" }), context: post.MachineId.ToString()); } diff --git a/src/ApiService/ApiService/Functions/Tasks.cs b/src/ApiService/ApiService/Functions/Tasks.cs index be5c8b42df..e2292cbcf5 100644 --- a/src/ApiService/ApiService/Functions/Tasks.cs +++ b/src/ApiService/ApiService/Functions/Tasks.cs @@ -64,7 +64,7 @@ private async Async.Task Get(HttpRequestData req) { return await RequestHandling.Ok(req, result); } - var tasks = await _context.TaskOperations.SearchAll().ToListAsync(); + var tasks = await _context.TaskOperations.SearchStates(request.OkV.JobId, request.OkV.State).ToListAsync(); var response2 = req.CreateResponse(HttpStatusCode.OK); await response2.WriteAsJsonAsync(tasks); return response2; diff --git a/src/ApiService/ApiService/onefuzzlib/ScalesetOperations.cs b/src/ApiService/ApiService/onefuzzlib/ScalesetOperations.cs index 2252263ea4..f771396fc6 100644 --- a/src/ApiService/ApiService/onefuzzlib/ScalesetOperations.cs +++ b/src/ApiService/ApiService/onefuzzlib/ScalesetOperations.cs @@ -77,7 +77,7 @@ public async Async.Task SyncScalesetSize(Scaleset scaleset) { _log.Info($"{SCALESET_LOG_PREFIX} unexpected scaleset size, resizing. scaleset_id: {scaleset.ScalesetId} expected:{scaleset.Size} actual:{size}"); scaleset = scaleset with { Size = size.Value }; - var replaceResult = await Update(scaleset); + var replaceResult = await Replace(scaleset); if (!replaceResult.IsOk) { _log.Error($"Failed to update scaleset size for scaleset {scaleset.ScalesetId} due to {replaceResult.ErrorV}"); } diff --git a/src/ApiService/ApiService/onefuzzlib/VmssOperations.cs b/src/ApiService/ApiService/onefuzzlib/VmssOperations.cs index c26ecda530..e63c1afdb0 100644 --- a/src/ApiService/ApiService/onefuzzlib/VmssOperations.cs +++ b/src/ApiService/ApiService/onefuzzlib/VmssOperations.cs @@ -107,7 +107,7 @@ private VirtualMachineScaleSetResource GetVmssResource(Guid name) { var res = await GetVmssResource(name).GetAsync(); _log.Verbose($"getting vmss: {name}"); return res.Value.Data; - } catch (Exception ex) when (ex is RequestFailedException) { + } catch (RequestFailedException ex) when (ex.Status == 404) { return null; } } diff --git a/src/ApiService/FunctionalTests/1f-api/ApiBase.cs b/src/ApiService/FunctionalTests/1f-api/ApiBase.cs index 5c32551bad..c03ac22518 100644 --- a/src/ApiService/FunctionalTests/1f-api/ApiBase.cs +++ b/src/ApiService/FunctionalTests/1f-api/ApiBase.cs @@ -1,10 +1,135 @@ -using System.Text.Json; +using System.Net; +using System.Text.Json; using System.Text.Json.Nodes; using Xunit; using Xunit.Abstractions; namespace FunctionalTests; +public static class JsonObjectExt { + public static JsonObject AddIfNotNullV(this JsonObject o, string name, T? v) { + if (v is not null) + o.Add(name, JsonValue.Create(v)); + return o; + } + + public static JsonObject AddIfNotNullEnumerableV(this JsonObject o, string name, IEnumerable? v) { + if (v is not null) + o.Add(name, JsonValue.Create(new JsonArray(v.Select(s => JsonValue.Create(s)).ToArray()))); + return o; + } + + + public static JsonObject AddV(this JsonObject o, string name, T v) { + o.Add(name, JsonValue.Create(v)); + return o; + } +} + +public static class JsonElementExt { + public static string GetRawTextProperty(this JsonElement e, string property) { + return e.GetProperty(property).GetRawText(); + } + + public static DateTimeOffset GetDateTimeOffsetProperty(this JsonElement e, string property) { + return e.GetProperty(property).GetDateTimeOffset()!; + } + + public static DateTimeOffset? GetNullableDateTimeOffsetProperty(this JsonElement e, string property) { + return e.GetProperty(property).GetDateTimeOffset(); + } + + public static Guid? GetNullableGuidProperty(this JsonElement e, string property) { + return e.GetProperty(property).ValueKind == JsonValueKind.Null ? null : e.GetProperty(property).GetGuid(); + } + + public static Guid GetGuidProperty(this JsonElement e, string property) { + return e.GetProperty(property).GetGuid(); + } + + public static bool? GetNullableBoolProperty(this JsonElement e, string property) { + return e.GetProperty(property).ValueKind == JsonValueKind.Null ? null : e.GetProperty(property).GetBoolean(); + } + + public static long? GetNullableLongProperty(this JsonElement e, string property) { + return e.GetProperty(property).ValueKind == JsonValueKind.Null ? null : e.GetProperty(property).GetInt64(); + } + + public static string? GetNullableStringProperty(this JsonElement e, string property) { + return e.GetProperty(property).ValueKind == JsonValueKind.Null ? null : e.GetProperty(property).GetString(); + } + + public static string GetStringProperty(this JsonElement e, string property) { + return e.GetProperty(property).GetString()!; + } + + public static long GetLongProperty(this JsonElement e, string property) { + return e.GetProperty(property).GetInt64(); + } + + public static int GetIntProperty(this JsonElement e, string property) { + return e.GetProperty(property).GetInt32(); + } + + public static bool GetBoolProperty(this JsonElement e, string property) { + return e.GetProperty(property).GetBoolean(); + } + + public static T GetObjectProperty(this JsonElement e, string property) where T : IFromJsonElement, new() { + return new T().Convert(e.GetProperty(property)!); + } + + public static T? GetNullableObjectProperty(this JsonElement e, string property) where T : IFromJsonElement, new() { + return e.GetProperty(property).ValueKind == JsonValueKind.Null ? default(T) : new T().Convert(e.GetProperty(property)!); + } + + public static IDictionary? GetNullableStringDictProperty(this JsonElement e, string property) { + return e.GetProperty(property).Deserialize>(); + } + + public static IDictionary GetStringDictProperty(this JsonElement e, string property) { + return e.GetProperty(property).Deserialize>()!; + } + + public static IDictionary GetDictProperty(this JsonElement e, string property) where T : IFromJsonElement, new() { + return new Dictionary( + e.GetProperty(property)!.Deserialize>()!.Select( + kv => KeyValuePair.Create(kv.Key, new T().Convert(kv.Value)) + ) + ); + } + + public static IEnumerable GetEnumerableGuidProperty(this JsonElement e, string property) { + return e.GetProperty(property)!.EnumerateArray().Select(e => e.GetGuid()!); + } + + + public static IEnumerable GetEnumerableStringProperty(this JsonElement e, string property) { + return e.GetProperty(property)!.EnumerateArray().Select(e => e.GetString()!); + } + + public static IEnumerable? GetEnumerableNullableStringProperty(this JsonElement e, string property) { + if (e.GetProperty(property).ValueKind == JsonValueKind.Null) + return null; + else + return e.GetProperty(property)!.EnumerateArray().Select(e => e.GetString()!); + } + + + public static IEnumerable GetEnumerableProperty(this JsonElement e, string property) where T : IFromJsonElement, new() { + return e.GetProperty(property).EnumerateArray().Select(e => new T().Convert(e)!); + } + + public static IEnumerable? GetEnumerableNullableProperty(this JsonElement e, string property) where T : IFromJsonElement, new() { + if (e.GetProperty(property).ValueKind == JsonValueKind.Null) + return null; + else + return e.GetProperty(property).EnumerateArray().Select(e => new T().Convert(e)!); + } + +} + + public interface IFromJsonElement { T Convert(JsonElement e); } @@ -14,6 +139,10 @@ public class BooleanResult : IFromJsonElement { public BooleanResult() { } public BooleanResult(JsonElement e) => _e = e; + public bool IsError => Error.IsError(_e); + + public Error? Error => new Error(_e); + public bool Result => _e.GetProperty("result").GetBoolean(); public BooleanResult Convert(JsonElement e) => new BooleanResult(e); @@ -30,6 +159,22 @@ public ApiBase(Uri endpoint, string relativeUri, Microsoft.OneFuzz.Service.Reque _endpoint = new Uri(endpoint, relativeUri); _output = output; } + public async Task> QueryGet(string? query) { + Uri uri; + if (String.IsNullOrEmpty(query)) + uri = _endpoint; + else + uri = new Uri($"{_endpoint}?{query}"); + var r = await _request.Get(uri); + if (r.IsSuccessStatusCode) { + return Result.Ok(r.Content.ReadAsStream()); + } else if (r.StatusCode == HttpStatusCode.InternalServerError) { + return Result.Error((r.StatusCode, null)); + } else { + var e = (await JsonDocument.ParseAsync(r.Content.ReadAsStream())).RootElement; + return Result.Error((r.StatusCode, new Error(e))); + } + } public async Task Get(JsonObject root) { var body = root.ToJsonString(); diff --git a/src/ApiService/FunctionalTests/1f-api/Authentication.cs b/src/ApiService/FunctionalTests/1f-api/Authentication.cs new file mode 100644 index 0000000000..8f6ef4d319 --- /dev/null +++ b/src/ApiService/FunctionalTests/1f-api/Authentication.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace FunctionalTests; + +public class Authentication : IFromJsonElement { + JsonElement _e; + + public Authentication() { } + public Authentication(JsonElement e) => _e = e; + + public string Password => _e.GetStringProperty("password"); + + public string PublicKey => _e.GetStringProperty("public_key"); + public string PrivateKey => _e.GetStringProperty("private_key"); + + public Authentication Convert(JsonElement e) => new Authentication(e); + +} diff --git a/src/ApiService/FunctionalTests/1f-api/Container.cs b/src/ApiService/FunctionalTests/1f-api/Container.cs new file mode 100644 index 0000000000..b64208a7eb --- /dev/null +++ b/src/ApiService/FunctionalTests/1f-api/Container.cs @@ -0,0 +1,48 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using Xunit.Abstractions; + +namespace FunctionalTests { + + public class ContainerInfo : IFromJsonElement { + JsonElement _e; + public ContainerInfo() { } + public ContainerInfo(JsonElement e) => _e = e; + public ContainerInfo Convert(JsonElement e) => new ContainerInfo(e); + public string Name => _e.GetStringProperty("name"); + public IDictionary? Metadata => _e.GetNullableStringDictProperty("metadata"); + public Uri SasUrl => new Uri(_e.GetStringProperty("sas_url")); + } + + public class ContainerApi : ApiBase { + + public ContainerApi(Uri endpoint, Microsoft.OneFuzz.Service.Request request, ITestOutputHelper output) : + base(endpoint, "/api/Containers", request, output) { + } + + public async Task, Error>> Get(string? name = null) { + var n = new JsonObject() + .AddIfNotNullV("name", name); + + var res = await Get(n); + return IEnumerableResult(res); + } + + public async Task Delete(string name, IDictionary? metadata = null) { + var n = new JsonObject() + .AddV("name", name) + .AddIfNotNullV("metadata", metadata); + + return Return(await Delete(n)); + } + + public async Task> Post(string name, IDictionary? metadata = null) { + var n = new JsonObject() + .AddV("name", name) + .AddIfNotNullV("metadata", metadata); + var res = await Post(n); + return Result(res); + } + } + +} diff --git a/src/ApiService/FunctionalTests/1f-api/Download.cs b/src/ApiService/FunctionalTests/1f-api/Download.cs new file mode 100644 index 0000000000..1cd0157ac0 --- /dev/null +++ b/src/ApiService/FunctionalTests/1f-api/Download.cs @@ -0,0 +1,21 @@ +using System.Net; +using System.Web; +using Xunit.Abstractions; + +namespace FunctionalTests { + public class DownloadApi : ApiBase { + + public DownloadApi(Uri endpoint, Microsoft.OneFuzz.Service.Request request, ITestOutputHelper output) : + base(endpoint, "/api/Download", request, output) { + } + + public async Task> Get(string? container = null, string? filename = null) { + var n = HttpUtility.ParseQueryString(string.Empty); + if (container is not null) + n.Add("container", container); + if (filename is not null) + n.Add("filename", filename); + return await QueryGet(n.ToString()); + } + } +} diff --git a/src/ApiService/FunctionalTests/1f-api/Error.cs b/src/ApiService/FunctionalTests/1f-api/Error.cs index be2a826029..984024aa13 100644 --- a/src/ApiService/FunctionalTests/1f-api/Error.cs +++ b/src/ApiService/FunctionalTests/1f-api/Error.cs @@ -6,14 +6,16 @@ namespace FunctionalTests; public class Error : IComparable, IFromJsonElement { JsonElement _e; + public Error() { } + public Error(JsonElement e) { _e = e; Assert.True(_e.EnumerateObject().Count() == 2); } - public int Code => _e.GetProperty("code").GetInt32(); + public int Code => _e.GetIntProperty("code"); - public IEnumerable Errors => _e.GetProperty("errors").EnumerateArray().Select(e => e.GetString()!); + public IEnumerable Errors => _e.GetEnumerableStringProperty("errors"); public Error Convert(JsonElement e) => new Error(e); @@ -49,5 +51,9 @@ public override string ToString() { public bool UnableToFindScalesetError => Code == 450 && Errors.First() == "unable to find scaleset"; - public bool UnableToFindNode => Code == 467 && Errors.First() == "unable to find node "; + public bool UnableToFindNode => Code == 467 && Errors.First() == "unable to find node"; + + public bool ShouldBeProvided(string p) => Code == 450 && Errors.First() == $"'{p}' query parameter must be provided"; + + public bool UnableToFindTask => Code == 450 && Errors.First() == "unable to find task"; } diff --git a/src/ApiService/FunctionalTests/1f-api/Info.cs b/src/ApiService/FunctionalTests/1f-api/Info.cs new file mode 100644 index 0000000000..c2a6bdf6d4 --- /dev/null +++ b/src/ApiService/FunctionalTests/1f-api/Info.cs @@ -0,0 +1,48 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using Xunit.Abstractions; + +namespace FunctionalTests { + + public class InfoVersion : IFromJsonElement { + JsonElement _e; + + public InfoVersion() { } + public InfoVersion(JsonElement e) => _e = e; + public InfoVersion Convert(JsonElement e) => new InfoVersion(e); + + public string Git => _e.GetProperty("git").GetString()!; + public string Build => _e.GetProperty("build").GetString()!; + public string Version => _e.GetProperty("version").GetString()!; + } + + + public class InfoResponse : IFromJsonElement { + JsonElement _e; + + public InfoResponse() { } + + public InfoResponse(JsonElement e) => _e = e; + + public InfoResponse Convert(JsonElement e) => new InfoResponse(e); + + public string ResourceGroup => _e.GetStringProperty("resource_group")!; + public string Region => _e.GetStringProperty("region")!; + public string Subscription => _e.GetStringProperty("subscription")!; + public IDictionary Versions => _e.GetDictProperty("property"); + } + + + class InfoApi : ApiBase { + + public InfoApi(Uri endpoint, Microsoft.OneFuzz.Service.Request request, ITestOutputHelper output) : + base(endpoint, "/api/Info", request, output) { + } + + public async Task> Get() { + var n = new JsonObject(); + var res = await Get(n); + return Result(res); + } + } +} diff --git a/src/ApiService/FunctionalTests/1f-api/Jobs.cs b/src/ApiService/FunctionalTests/1f-api/Jobs.cs new file mode 100644 index 0000000000..faf666bd14 --- /dev/null +++ b/src/ApiService/FunctionalTests/1f-api/Jobs.cs @@ -0,0 +1,78 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using Xunit.Abstractions; + +namespace FunctionalTests; + +public class JobTaskInfo : IFromJsonElement { + JsonElement _e; + + public JobTaskInfo() { } + + public JobTaskInfo(JsonElement e) => _e = e; + + public JobTaskInfo Convert(JsonElement e) => new JobTaskInfo(e); + + public Guid TaskId => _e.GetGuidProperty("task_id"); + + public string Type => _e.GetStringProperty("type"); + + public string State => _e.GetStringProperty("state"); +} + + +public class Job : IFromJsonElement { + JsonElement _e; + + public Job() { } + public Job(JsonElement e) => _e = e; + + public Job Convert(JsonElement e) => new Job(e); + + public Guid JobId => _e.GetGuidProperty("job_id"); + + public string State => _e.GetStringProperty("state"); + + public string? Error => _e.GetNullableStringProperty("error"); + + public DateTimeOffset? EndTime => _e.GetNullableDateTimeOffsetProperty("end_time"); + + public IEnumerable? TaskInfo => _e.GetEnumerableNullableProperty("task_info"); +} + +public class Jobs : ApiBase { + + public Jobs(Uri endpoint, Microsoft.OneFuzz.Service.Request request, ITestOutputHelper output) : + base(endpoint, "/api/Jobs", request, output) { + } + + public async Task, Error>> Get(Guid? jobId = null, List? state = null, List? taskState = null, bool? withTasks = null) { + var n = new JsonObject() + .AddIfNotNullV("job_id", jobId) + .AddIfNotNullV("state", state) + .AddIfNotNullV("task_state", taskState) + .AddIfNotNullV("with_tasks", withTasks); + + var r = await Get(n); + return IEnumerableResult(r); + } + + + public async Task> Post(string project, string name, string build, long duration, string? logs = null) { + var n = new JsonObject() + .AddV("project", project) + .AddV("name", name) + .AddV("build", build) + .AddV("duration", duration) + .AddIfNotNullV("logs", logs); + + var r = await Post(n); + return Result(r); + } + + public async Task> Delete(Guid jobId) { + var n = new JsonObject().AddV("job_id", jobId); + var r = await Delete(n); + return Result(r); + } +} diff --git a/src/ApiService/FunctionalTests/1f-api/Negotiate.cs b/src/ApiService/FunctionalTests/1f-api/Negotiate.cs new file mode 100644 index 0000000000..464cc4bf17 --- /dev/null +++ b/src/ApiService/FunctionalTests/1f-api/Negotiate.cs @@ -0,0 +1,4 @@ +namespace FunctionalTests { + public class NegotiateApi { + } +} diff --git a/src/ApiService/FunctionalTests/1f-api/Node.cs b/src/ApiService/FunctionalTests/1f-api/Node.cs index a814f4a039..11bbf5718f 100644 --- a/src/ApiService/FunctionalTests/1f-api/Node.cs +++ b/src/ApiService/FunctionalTests/1f-api/Node.cs @@ -12,24 +12,24 @@ public Node() { } public Node(JsonElement e) => _e = e; - public string PoolName => _e.GetProperty("pool_name").GetString()!; + public string PoolName => _e.GetStringProperty("pool_name"); - public Guid MachineId => _e.GetProperty("machine_id").GetGuid(); + public Guid MachineId => _e.GetGuidProperty("machine_id"); - public Guid? PoolId => _e.GetProperty("pool_id").ValueKind == JsonValueKind.Null ? null : _e.GetProperty("pool_id").GetGuid(); + public Guid? PoolId => _e.GetNullableGuidProperty("pool_id"); - public string Version => _e.GetProperty("version").GetString()!; + public string Version => _e.GetStringProperty("version"); - public DateTimeOffset? HeartBeat => _e.GetProperty("heart_beat").ValueKind == JsonValueKind.Null ? null : _e.GetProperty("heart_beat").GetDateTimeOffset(); + public DateTimeOffset? HeartBeat => _e.GetNullableDateTimeOffsetProperty("heart_beat"); - public DateTimeOffset? InitializedAt => _e.GetProperty("initialized_at").ValueKind == JsonValueKind.Null ? null : _e.GetProperty("initialized_at").GetDateTimeOffset(); + public DateTimeOffset? InitializedAt => _e.GetNullableDateTimeOffsetProperty("initialized_at"); - public string State => _e.GetProperty("state").GetString()!; + public string State => _e.GetStringProperty("state"); - public Guid? ScalesetId => _e.GetProperty("scaleset_id").ValueKind == JsonValueKind.Null ? null : _e.GetProperty("scaleset_id").GetGuid(); - public bool ReimageRequested => _e.GetProperty("reimage_requested").GetBoolean(); - public bool DeleteRequested => _e.GetProperty("delete_requested").GetBoolean(); - public bool DebugKeepNode => _e.GetProperty("debug_keep_node").GetBoolean(); + public Guid? ScalesetId => _e.GetNullableGuidProperty("scaleset_id"); + public bool ReimageRequested => _e.GetBoolProperty("reimage_requested"); + public bool DeleteRequested => _e.GetBoolProperty("delete_requested"); + public bool DebugKeepNode => _e.GetBoolProperty("debug_keep_node"); public Node Convert(JsonElement e) => new Node(e); } @@ -42,39 +42,28 @@ public NodeApi(Uri endpoint, Microsoft.OneFuzz.Service.Request request, ITestOut } public async Task Update(Guid machineId, bool? debugKeepNode = null) { - var j = new JsonObject(); - if (debugKeepNode is not null) - j.Add("debug_keep_node", JsonValue.Create(debugKeepNode)); - - j.Add("machine_id", JsonValue.Create(machineId)); + var j = new JsonObject() + .AddIfNotNullV("debug_keep_node", debugKeepNode) + .AddV("machine_id", machineId); return Return(await Post(j)); } - public async Task, Error>> Get(Guid? machineId = null, List? state = null, Guid? scalesetId = null, string? poolName = null) { - var j = new JsonObject(); - if (machineId is not null) - j.Add("machine_id", JsonValue.Create(machineId)); - if (state is not null) { - var states = new JsonArray(state.Select(s => JsonValue.Create(s)).ToArray()); - j.Add("state", JsonValue.Create(states)); - } - if (scalesetId is not null) - j.Add("scaleset_id", JsonValue.Create(scalesetId)); - - if (poolName is not null) - j.Add("pool_name", JsonValue.Create(poolName)); + public async Task, Error>> Get(Guid? machineId = null, IEnumerable? state = null, Guid? scalesetId = null, string? poolName = null) { + var j = new JsonObject() + .AddIfNotNullV("machine_id", machineId) + .AddIfNotNullEnumerableV("state", state) + .AddIfNotNullV("scaleset_id", scalesetId) + .AddIfNotNullV("pool_name", poolName); return IEnumerableResult(await Get(j)); } public async Task Patch(Guid machineId) { - var j = new JsonObject(); - j.Add("machine_id", JsonValue.Create(machineId)); + var j = new JsonObject().AddV("machine_id", machineId); return Return(await Patch(j)); } public async Task Delete(Guid machineId) { - var j = new JsonObject(); - j.Add("machine_id", JsonValue.Create(machineId)); + var j = new JsonObject().AddV("machine_id", machineId); return Return(await Delete(j)); } diff --git a/src/ApiService/FunctionalTests/1f-api/NodeAddSshKey.cs b/src/ApiService/FunctionalTests/1f-api/NodeAddSshKey.cs new file mode 100644 index 0000000000..f29cad8339 --- /dev/null +++ b/src/ApiService/FunctionalTests/1f-api/NodeAddSshKey.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Nodes; +using Xunit.Abstractions; + + +namespace FunctionalTests { + public class NodeAddSshKeyApi : ApiBase { + + public NodeAddSshKeyApi(Uri endpoint, Microsoft.OneFuzz.Service.Request request, ITestOutputHelper output) : + base(endpoint, "/api/node_add_ssh_key", request, output) { + } + + public async Task Post(Guid machineId, string publicSshKey) { + var n = new JsonObject() + .AddV("machine_id", machineId) + .AddV("public_key", publicSshKey); + + var r = await Post(n); + return Return(r); + } + } +} diff --git a/src/ApiService/FunctionalTests/1f-api/Notifications.cs b/src/ApiService/FunctionalTests/1f-api/Notifications.cs new file mode 100644 index 0000000000..7ab4a14918 --- /dev/null +++ b/src/ApiService/FunctionalTests/1f-api/Notifications.cs @@ -0,0 +1,55 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using Xunit.Abstractions; + +namespace FunctionalTests; + +public class Notification : IFromJsonElement { + JsonElement _e; + + public Notification() { } + + public Notification(JsonElement e) => _e = e; + + public Notification Convert(JsonElement e) => new Notification(e); + + public Guid NotificationId => _e.GetGuidProperty("notification_id"); + + public string Container => _e.GetStringProperty("container"); + + public string Config => _e.GetRawTextProperty("config"); +} + +public class NotificationsApi : ApiBase { + public NotificationsApi(Uri endpoint, Microsoft.OneFuzz.Service.Request request, ITestOutputHelper output) : + base(endpoint, "/api/Notifications", request, output) { + } + + public async Task, Error>> Get(List? containers = null) { + var n = new JsonObject() + .AddIfNotNullEnumerableV("container", containers); + + var r = await Get(n); + return IEnumerableResult(r); + } + + + public async Task> Post(string container, bool replaceExisting, string config) { + var n = new JsonObject() + .AddV("container", container) + .AddV("replace_existing", replaceExisting) + .AddV("config", config); + + var r = await Post(n); + return Result(r); + } + + + public async Task> Delete(Guid notificationId) { + var n = new JsonObject() + .AddV("notification_id", notificationId); + + var r = await Delete(n); + return Result(r); + } +} diff --git a/src/ApiService/FunctionalTests/1f-api/Pool.cs b/src/ApiService/FunctionalTests/1f-api/Pool.cs index ce5a61e617..a999b3da5d 100644 --- a/src/ApiService/FunctionalTests/1f-api/Pool.cs +++ b/src/ApiService/FunctionalTests/1f-api/Pool.cs @@ -11,12 +11,11 @@ public class Pool : IFromJsonElement { public Pool() { } public Pool(JsonElement e) => _e = e; - - public string Name => _e.GetProperty("name").GetString()!; - public string PoolId => _e.GetProperty("pool_id").GetString()!; - public string Os => _e.GetProperty("os").GetString()!; - public Pool Convert(JsonElement e) => new Pool(e); + + public string Name => _e.GetStringProperty("name"); + public string PoolId => _e.GetStringProperty("pool_id"); + public string Os => _e.GetStringProperty("os"); } public class PoolApi : ApiBase { @@ -29,21 +28,17 @@ public PoolApi(Uri endpoint, Microsoft.OneFuzz.Service.Request request, ITestOut public async Task Delete(string name, bool now = true) { _output.WriteLine($"deleting pool: {name}, now: {now}"); - var root = new JsonObject(); - root.Add("name", JsonValue.Create(name)); - root.Add("now", JsonValue.Create(now)); + var root = new JsonObject() + .AddV("name", name) + .AddV("now", now); return Return(await Delete(root)); } public async Task, Error>> Get(string? name = null, string? id = null, string? state = null) { - var root = new JsonObject(); - if (id is not null) - root.Add("pool_id", id); - if (name is not null) - root.Add("name", name); - if (state is not null) - root.Add("state", state); - + var root = new JsonObject() + .AddIfNotNullV("pool_id", id) + .AddIfNotNullV("name", name) + .AddIfNotNullV("state", state); var res = await Get(root); return IEnumerableResult(res); } @@ -62,14 +57,14 @@ public async Task DeleteAll() { } } - public async Task> Create(string poolName, string os, string arch = "x86_64") { + public async Task> Create(string poolName, string os, string arch = "x86_64", bool managed = true) { _output.WriteLine($"creating new pool {poolName} os: {os}"); - var rootPoolCreate = new JsonObject(); - rootPoolCreate.Add("name", poolName); - rootPoolCreate.Add("os", os); - rootPoolCreate.Add("arch", arch); - rootPoolCreate.Add("managed", true); + var rootPoolCreate = new JsonObject() + .AddV("name", poolName) + .AddV("os", os) + .AddV("arch", arch) + .AddV("managed", managed); return Result(await Post(rootPoolCreate)); } } diff --git a/src/ApiService/FunctionalTests/1f-api/Proxy.cs b/src/ApiService/FunctionalTests/1f-api/Proxy.cs index 98f51a6762..8dc7006f0a 100644 --- a/src/ApiService/FunctionalTests/1f-api/Proxy.cs +++ b/src/ApiService/FunctionalTests/1f-api/Proxy.cs @@ -9,10 +9,10 @@ public class Proxy : IFromJsonElement { public Proxy() { } public Proxy(JsonElement e) => _e = e; - public string Region => _e.GetProperty("region").GetString()!; - public Guid ProxyId => _e.GetProperty("proxy_id").GetGuid()!; + public string Region => _e.GetStringProperty("region"); + public Guid ProxyId => _e.GetGuidProperty("proxy_id"); - public string VmState => _e.GetProperty("state").GetString()!; + public string VmState => _e.GetStringProperty("state"); public Proxy Convert(JsonElement e) => new Proxy(e); } @@ -22,10 +22,10 @@ public class Forward : IFromJsonElement, IComparable { public Forward() { } public Forward(JsonElement e) => _e = e; - public long SrcPort => _e.GetProperty("src_port").GetInt64(); - public long DstPort => _e.GetProperty("dst_port").GetInt64(); + public long SrcPort => _e.GetLongProperty("src_port"); + public long DstPort => _e.GetLongProperty("dst_port"); - public string DstIp => _e.GetProperty("dst_ip").GetString()!; + public string DstIp => _e.GetStringProperty("dst_ip"); public Forward Convert(JsonElement e) => new Forward(e); @@ -93,44 +93,35 @@ public ProxyApi(Uri endpoint, Microsoft.OneFuzz.Service.Request request, ITestOu } public async Task, Error>> Get(Guid? scalesetId = null, Guid? machineId = null, int? dstPort = null) { - var root = new JsonObject(); - if (scalesetId is not null) - root.Add("scaleset_id", scalesetId); - if (machineId is not null) - root.Add("machine_id", machineId); - if (dstPort is not null) - root.Add("dst_port", dstPort); + var root = new JsonObject() + .AddIfNotNullV("scaleset_id", scalesetId) + .AddIfNotNullV("machine_id", machineId) + .AddIfNotNullV("dst_port", dstPort); var r = await Get(root); - if (Error.IsError(r)) { - return Result, Error>.Error(new Error(r)); - } else { - return IEnumerableResult(r.GetProperty("proxies")); - } + return IEnumerableResult(r.GetProperty("proxies")); } public async Task Delete(Guid scalesetId, Guid machineId, int? dstPort = null) { - var root = new JsonObject(); - root.Add("scaleset_id", scalesetId); - root.Add("machine_id", machineId); - if (dstPort != null) - root.Add("dst_port", dstPort); + var root = new JsonObject() + .AddV("scaleset_id", scalesetId) + .AddV("machine_id", machineId) + .AddIfNotNullV("dst_port", dstPort); return Return(await Delete(root)); } public async Task Reset(string region) { - var root = new JsonObject(); - root.Add("region", region); + var root = new JsonObject().AddV("region", region); var r = await Patch(root); return Return(r); } public async Task> Create(Guid scalesetId, Guid machineId, int dstPort, int duration) { - var root = new JsonObject(); - root.Add("scaleset_id", scalesetId); - root.Add("machine_id", machineId); - root.Add("dst_port", dstPort); - root.Add("duration", duration); + var root = new JsonObject() + .AddV("scaleset_id", scalesetId) + .AddV("machin_id", machineId) + .AddV("dst_port", dstPort) + .AddV("duration", duration); var r = await Post(root); return Result(r); diff --git a/src/ApiService/FunctionalTests/1f-api/ReproVmss.cs b/src/ApiService/FunctionalTests/1f-api/ReproVmss.cs new file mode 100644 index 0000000000..554d8089ca --- /dev/null +++ b/src/ApiService/FunctionalTests/1f-api/ReproVmss.cs @@ -0,0 +1,83 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using Xunit.Abstractions; + +namespace FunctionalTests; + +public class ReproConfig : IFromJsonElement { + JsonElement _e; + + public ReproConfig() { } + public ReproConfig(JsonElement e) => _e = e; + + public ReproConfig Convert(JsonElement e) => new ReproConfig(e); + + public string Container => _e.GetStringProperty("container"); + public string Path => _e.GetStringProperty("path"); + + public long Duration => _e.GetLongProperty("duration"); +} + +public class Repro : IFromJsonElement { + + JsonElement _e; + + public Repro() { } + public Repro(JsonElement e) => _e = e; + + public Repro Convert(JsonElement e) => new Repro(e); + + public Guid VmId => _e.GetGuidProperty("vm_id"); + + public Guid TaskId => _e.GetGuidProperty("task_id"); + + public string Os => _e.GetStringProperty("os"); + + public string? Ip => _e.GetNullableStringProperty("ip"); + + public DateTimeOffset? EndTime => _e.GetNullableDateTimeOffsetProperty("end_time"); + + public string State => _e.GetStringProperty("state"); + + public Error? Error => _e.GetNullableObjectProperty("error"); + + public Authentication? Auth => _e.GetNullableObjectProperty("auth"); + + ReproConfig Config => _e.GetObjectProperty("config"); + + UserInfo? UserInfo => _e.GetNullableObjectProperty("user_info"); +} + + +public class ReproVmss : ApiBase { + + + public ReproVmss(Uri endpoint, Microsoft.OneFuzz.Service.Request request, ITestOutputHelper output) : + base(endpoint, "/api/repro_vms", request, output) { + } + + public async Task> Get(Guid? vmId) { + var n = new JsonObject().AddV("vm_id", vmId); + + var r = await Get(n); + return Result(r); + } + + public async Task> Post(string container, string path, long duration) { + var n = new JsonObject() + .AddV("container", container) + .AddV("path", path) + .AddV("duration", duration); + + var r = await Post(n); + return Result(r); + } + + public async Task> Delete(Guid? vmId) { + var n = new JsonObject() + .AddV("vm_id", vmId); + var r = await Delete(n); + return Result(r); + } + +} diff --git a/src/ApiService/FunctionalTests/1f-api/Scaleset.cs b/src/ApiService/FunctionalTests/1f-api/Scaleset.cs index 969e913f41..5998a1d458 100644 --- a/src/ApiService/FunctionalTests/1f-api/Scaleset.cs +++ b/src/ApiService/FunctionalTests/1f-api/Scaleset.cs @@ -4,65 +4,55 @@ using Xunit.Abstractions; namespace FunctionalTests; -public class Authentication { - JsonElement _e; - - public Authentication() { } - public Authentication(JsonElement e) => _e = e; - - string Password => _e.GetProperty("password").GetString()!; - - string PublicKey => _e.GetProperty("public_key").GetString()!; - string PrivateKey => _e.GetProperty("private_key").GetString()!; -} - -public class ScalesetNodeState { +public class ScalesetNodeState : IFromJsonElement { JsonElement _e; public ScalesetNodeState() { } public ScalesetNodeState(JsonElement e) => _e = e; - public Guid MachineId => _e.GetProperty("machine_id").GetGuid(); - public string InstanceId => _e.GetProperty("instance_id").GetString()!; + public ScalesetNodeState Convert(JsonElement e) => new ScalesetNodeState(e); - public string? NodeState => _e.GetProperty("state").GetString(); + public Guid MachineId => _e.GetGuidProperty("machine_id"); + public string InstanceId => _e.GetStringProperty("instance_id"); + + public string? NodeState => _e.GetNullableStringProperty("state"); } public class Scaleset : IFromJsonElement { JsonElement _e; public Scaleset() { } public Scaleset(JsonElement e) => _e = e; + public Scaleset Convert(JsonElement e) => new Scaleset(e); - public Guid ScalesetId => _e.GetProperty("scaleset_id").GetGuid(); - public string PoolName => _e.GetProperty("pool_name").GetString()!; - public string State => _e.GetProperty("state").GetString()!; + public Guid ScalesetId => _e.GetGuidProperty("scaleset_id"); + public string PoolName => _e.GetStringProperty("pool_name"); + public string State => _e.GetStringProperty("state"); - public Error? Error => _e.GetProperty("error").ValueKind == JsonValueKind.Null ? null : new Error(_e.GetProperty("error")); - public int Size => _e.GetProperty("size").GetInt32(); + public Error? Error => _e.GetNullableObjectProperty("error"); - public string VmSku => _e.GetProperty("vm_sku").GetString()!; - public string Image => _e.GetProperty("image").GetString()!; + public long Size => _e.GetLongProperty("size"); - public string Region => _e.GetProperty("region").GetString()!; + public string VmSku => _e.GetStringProperty("vm_sku"); + public string Image => _e.GetStringProperty("image"); - public bool? SpotInstance => _e.GetProperty("spot_instance").ValueKind == JsonValueKind.Null ? null : _e.GetProperty("spot_instance").GetBoolean(); + public string Region => _e.GetStringProperty("region"); - public bool EphemeralOsDisks => _e.GetProperty("ephemeral_os_disks").GetBoolean(); + public bool? SpotInstance => _e.GetNullableBoolProperty("spot_instance"); - public bool NeedsConfigUpdate => _e.GetProperty("needs_config_update").GetBoolean(); + public bool EphemeralOsDisks => _e.GetBoolProperty("ephemeral_os_disks"); - public Dictionary Tags => _e.GetProperty("tags").Deserialize>()!; + public bool NeedsConfigUpdate => _e.GetBoolProperty("needs_config_update"); - public Authentication? Auth => _e.GetProperty("auth").ValueKind == JsonValueKind.Null ? null : new Authentication(_e.GetProperty("auth")); + public IDictionary Tags => _e.GetStringDictProperty("tags"); - public Guid? ClientId => _e.GetProperty("client_id").ValueKind == JsonValueKind.Null ? null : _e.GetProperty("client_id").GetGuid(); + public Authentication? Auth => _e.GetNullableObjectProperty("auth"); - public Guid? ClientObjectId => _e.GetProperty("client_object_id").ValueKind == JsonValueKind.Null ? null : _e.GetProperty("client_object_id").GetGuid(); + public Guid? ClientId => _e.GetNullableGuidProperty("client_id"); - public List? Nodes => _e.GetProperty("nodes").ValueKind == JsonValueKind.Null ? null : _e.GetProperty("nodes").EnumerateArray().Select(node => new ScalesetNodeState(node)).ToList(); + public Guid? ClientObjectId => _e.GetNullableGuidProperty("client_object_id"); - public Scaleset Convert(JsonElement e) => new Scaleset(e); + public IEnumerable? Nodes => _e.GetEnumerableNullableProperty("nodes"); } public class ScalesetApi : ApiBase { @@ -75,47 +65,41 @@ public ScalesetApi(Uri endpoint, Microsoft.OneFuzz.Service.Request request, ITes public async Task, Error>> Get(Guid? id = null, string? state = null, bool? includeAuth = false) { - var root = new JsonObject(); - if (id is not null) - root.Add("scaleset_id", id); - if (state is not null) - root.Add("state", state); - if (includeAuth is not null) - root.Add("include_auth", includeAuth); - var res = await Get(root); + var j = new JsonObject() + .AddIfNotNullV("scaleset_id", id) + .AddIfNotNullV("state", state) + .AddIfNotNullV("include_auth", includeAuth); + var res = await Get(j); return IEnumerableResult(res); } public async Task> Create(string poolName, int size, string? region = null, string vmSku = "Standard_D2s_v3", string image = Image_Ubuntu_20_04, bool spotInstance = false) { _output.WriteLine($"Creating scaleset in pool {poolName}, size: {size}"); - var rootScalesetCreate = new JsonObject(); - rootScalesetCreate.Add("pool_name", poolName); - rootScalesetCreate.Add("vm_sku", vmSku); - rootScalesetCreate.Add("image", image); - rootScalesetCreate.Add("size", size); - rootScalesetCreate.Add("spot_instances", spotInstance); - if (region is not null) - rootScalesetCreate.Add("region", region); - - var tags = new JsonObject(); - tags.Add("Purpose", "Functional-Test"); - rootScalesetCreate.Add("tags", tags); + + var rootScalesetCreate = new JsonObject() + .AddV("pool_name", poolName) + .AddV("vm_sku", vmSku) + .AddV("image", image) + .AddV("size", size) + .AddV("spot_instances", spotInstance) + .AddIfNotNullV("region", region); + + rootScalesetCreate.Add("tags", new JsonObject().AddV("Purpose", "Functional-Test")); return Result(await Post(rootScalesetCreate)); } public async Task> Patch(Guid id, int size) { - var scalesetPatch = new JsonObject(); - scalesetPatch.Add("scaleset_id", id); - scalesetPatch.Add("size", size); + var scalesetPatch = new JsonObject() + .AddV("scaleset_id", id) + .AddV("size", size); return Result(await Patch(scalesetPatch)); } public async Task Delete(Guid id, bool now) { - var scalesetDelete = new JsonObject(); - scalesetDelete.Add("scaleset_id", id); - scalesetDelete.Add("now", now); - + var scalesetDelete = new JsonObject() + .AddV("scaleset_id", id) + .AddV("now", now); return Return(await Delete(scalesetDelete)); } diff --git a/src/ApiService/FunctionalTests/1f-api/Tasks.cs b/src/ApiService/FunctionalTests/1f-api/Tasks.cs new file mode 100644 index 0000000000..a5423a93b8 --- /dev/null +++ b/src/ApiService/FunctionalTests/1f-api/Tasks.cs @@ -0,0 +1,239 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using Xunit.Abstractions; + +namespace FunctionalTests; + + +public class TaskDetails { + JsonElement _e; + public TaskDetails() { } + public TaskDetails(JsonElement e) => _e = e; + + public string Type => _e.GetStringProperty("type"); + + public long Duration => _e.GetLongProperty("duration"); + public string? TargetExe => _e.GetStringProperty("target_exe"); + + public IDictionary? TargetEnv => _e.GetNullableStringDictProperty("target_env"); + + public IEnumerable? TargetOptions => _e.GetEnumerableStringProperty("target_options"); + + public long? TargetWorkers => _e.GetNullableLongProperty("target_workers"); + + public bool? TargetOptionsMerge => _e.GetNullableBoolProperty("target_option_merge"); + + public bool? CheckAsanLog => _e.GetNullableBoolProperty("check_asan_log"); + + public bool? CheckDebugger => _e.GetNullableBoolProperty("check_debugger"); + + public long? CheckRetryCount => _e.GetNullableLongProperty("check_retry_count"); + + public bool? CheckFuzzerHelp => _e.GetNullableBoolProperty("check_fuzzer_help"); + + public bool? ExpectCrashOnFailure => _e.GetNullableBoolProperty("expect_crash_on_failure"); + + public bool? RenameOutput => _e.GetNullableBoolProperty("rename_output"); + + public string? SupervisorExe => _e.GetNullableStringProperty("supervisor_exe"); + + public IDictionary? SupervisonEnv => _e.GetNullableStringDictProperty("supervisor_env"); + + public IEnumerable? SupervisorOptions => _e.GetEnumerableNullableStringProperty("supervisor_options"); + + public string? SupervisorInputMarker => _e.GetStringProperty("supervison_input_marker"); + + public string? GeneraroExe => _e.GetStringProperty("generator_exe"); + + public IDictionary? GeneratorEnv => _e.GetStringDictProperty("generator_env"); + + public IEnumerable? GeneratorOptions => _e.GetEnumerableNullableStringProperty("generator_options"); + + public string? AnalyzerExe => _e.GetNullableStringProperty("analyzer_exe"); + + public IDictionary? AnalyzerEnv => _e.GetNullableStringDictProperty("analyzer_env"); + + public IEnumerable? AnalyzerOptions => _e.GetEnumerableNullableStringProperty("analyzer_options"); + + public string? StatsFile => _e.GetNullableStringProperty("stats_file"); + + public bool? RebootAfterSetup => _e.GetNullableBoolProperty("reboot_after_setup"); + + public long? TargetTimeout => _e.GetNullableLongProperty("target_timeout"); + + public long? EnsembleSyncDelay => _e.GetNullableLongProperty("ensemble_sync_delay"); + + public bool? PreserveExistingOutputs => _e.GetNullableBoolProperty("preserve_existing_outputs"); + + public IEnumerable? ReportList => _e.GetEnumerableNullableStringProperty("report_list"); + + public long? MinimizedStackDepth => _e.GetNullableLongProperty("minimized_stack_depth"); + + public string? CoverageFilter => _e.GetNullableStringProperty("coverage_filter"); + + public string? WaitForFiles => _e.GetNullableStringProperty("wait_for_files"); + public string? StatsFormat => _e.GetNullableStringProperty("stats_format"); +} + + +public class TaskConfig : IFromJsonElement { + JsonElement _e; + public TaskConfig() { } + public TaskConfig(JsonElement e) => _e = e; + public TaskConfig Convert(JsonElement e) => new TaskConfig(e); + + public Guid JobId => _e.GetGuidProperty("job_id"); + public IEnumerable? PrereqTasks => _e.GetEnumerableGuidProperty("prereq_tasks"); +} + +public class OneFuzzTask : IFromJsonElement { + JsonElement _e; + + public OneFuzzTask() { } + public OneFuzzTask(JsonElement e) => _e = e; + + public OneFuzzTask Convert(JsonElement e) => new OneFuzzTask(e); + + public Guid JobId => _e.GetGuidProperty("job_id"); + public Guid TaskId => _e.GetGuidProperty("task_id"); + public string State => _e.GetStringProperty("state"); + public string Os => _e.GetStringProperty("os"); + public Error? Error => _e.GetNullableObjectProperty("error"); + + public DateTimeOffset? Heartbeat => _e.GetNullableDateTimeOffsetProperty("heartbeat"); + public DateTimeOffset? EndTime => _e.GetNullableDateTimeOffsetProperty("end_time"); + + public Authentication? Auth => _e.GetNullableObjectProperty("auth"); + + public UserInfo? UserInfo => _e.GetNullableObjectProperty("user_info"); + + public TaskConfig Config => _e.GetObjectProperty("task_config"); +} + + +public class TaskApi : ApiBase { + + public TaskApi(Uri endpoint, Microsoft.OneFuzz.Service.Request request, ITestOutputHelper output) : + base(endpoint, "/api/tasks", request, output) { + } + + public static JsonObject TaskDetails( + string taskType, + long duration, + string? targetExe = null, + IDictionary? targetEnv = null, + IEnumerable? targetOptions = null, + long? targetWorkers = null, + bool? targetOptionsMerge = null, + bool? checkAsanLog = null, + bool? checkDebugger = null, + long? checkRetryCount = null, + bool? checkFuzzerHelp = null, + bool? expectCrashOnFailure = null, + bool? renameOutput = null, + string? supervisorExe = null, + IDictionary? supervisorEnv = null, + IEnumerable? supervisorOptions = null, + string? supervisorInputMarker = null, + string? generatorExe = null, + IDictionary? generatorEnv = null, + IEnumerable? generatorOptions = null, + string? analyzerExe = null, + IDictionary? analyzerEnv = null, + IEnumerable? analyzerOptions = null, + string? waitForFiles = null, + string? statsFile = null, + string? statsFormat = null, + bool? rebootAfterSetup = null, + long? targetTimeout = null, + long? ensembleSyncDelay = null, + bool? preserveExistingOutputs = null, + IEnumerable? reportList = null, + long? minimizedStackDepth = null, + string? coverageFilter = null + ) { + + return new JsonObject() + .AddV("task_type", taskType) + .AddV("duration", duration) + .AddIfNotNullV("target_exe", targetExe) + .AddIfNotNullV("target_env", targetEnv) + .AddIfNotNullV("target_options", targetOptions) + .AddIfNotNullV("target_workers", targetWorkers) + .AddIfNotNullV("target_options_merge", targetOptionsMerge) + .AddIfNotNullV("check_asan_log", checkAsanLog) + .AddIfNotNullV("check_debugger", checkDebugger) + .AddIfNotNullV("check_retry_count", checkRetryCount) + .AddIfNotNullV("check_fuzzer_help", checkFuzzerHelp) + .AddIfNotNullV("expect_crash_on_failure", expectCrashOnFailure) + .AddIfNotNullV("rename_output", renameOutput) + .AddIfNotNullV("supervisor_exe", supervisorExe) + .AddIfNotNullV("supervisor_env", supervisorEnv) + .AddIfNotNullV("supervisor_options", supervisorOptions) + .AddIfNotNullV("supervisor_input_marker", supervisorInputMarker) + .AddIfNotNullV("generator_exe", generatorExe) + .AddIfNotNullV("generator_env", generatorEnv) + .AddIfNotNullV("generator_options", generatorOptions) + .AddIfNotNullV("analyzer_exe", analyzerExe) + .AddIfNotNullV("analyzer_env", analyzerEnv) + .AddIfNotNullV("analyzer_options", analyzerOptions) + .AddIfNotNullV("wait_for_files", waitForFiles) + .AddIfNotNullV("stats_file", statsFile) + .AddIfNotNullV("stats_format", statsFormat) + .AddIfNotNullV("reboot_after_setup", rebootAfterSetup) + .AddIfNotNullV("target_timeout", targetTimeout) + .AddIfNotNullV("ensemble_sync_delay", ensembleSyncDelay) + .AddIfNotNullV("preserve_existing_outputs", preserveExistingOutputs) + .AddIfNotNullV("reprot_list", reportList) + .AddIfNotNullV("minimized_stack_depth", minimizedStackDepth) + .AddIfNotNullV("coverage_filter", coverageFilter); + } + + + public async Task, Error>> Get(Guid? jobId = null, Guid? taskId = null, List? state = null) { + var n = new JsonObject() + .AddIfNotNullV("job_id", jobId) + .AddIfNotNullV("task_id", taskId) + .AddIfNotNullV("state", state); + var r = await Get(n); + return IEnumerableResult(r); + } + + + public async Task> Post( + Guid jobId, + JsonObject taskDetails, + string taskPoolName, + long taskPoolCount, + IEnumerable? prereqTasks = null, + IEnumerable<(string, string)>? containers = null, + IDictionary? tags = null, + bool? colocate = null + ) { + + var j = new JsonObject() + .AddV("job_id", jobId) + .AddIfNotNullEnumerableV("prereq_tasks", prereqTasks) + .AddIfNotNullEnumerableV("containers", containers) + .AddIfNotNullV("tags", tags) + .AddIfNotNullV("colocate", colocate) + ; + + var pool = new JsonObject() + .AddV("count", taskPoolCount) + .AddV("pool_name", taskPoolName); + + j.Add("task", taskDetails); + j.Add("pool", pool); + + var r = await Post(j); + return Result(r); + } + + public async Task Delete(Guid taskId) { + var j = new JsonObject().AddV("task_id", taskId); + + var r = await Delete(j); + return new BooleanResult(r); + } +} diff --git a/src/ApiService/FunctionalTests/1f-api/UserInfo.cs b/src/ApiService/FunctionalTests/1f-api/UserInfo.cs new file mode 100644 index 0000000000..42f13479b5 --- /dev/null +++ b/src/ApiService/FunctionalTests/1f-api/UserInfo.cs @@ -0,0 +1,16 @@ +using System.Text.Json; + +namespace FunctionalTests; + +public class UserInfo : IFromJsonElement { + + JsonElement _e; + public UserInfo() { } + public UserInfo(JsonElement e) => _e = e; + public UserInfo Convert(JsonElement e) => new UserInfo(e); + + public Guid? ApplicationId => _e.GetNullableGuidProperty("application_id"); + public Guid? ObjectId => _e.GetNullableGuidProperty("object_id"); + public string? Upn => _e.GetNullableStringProperty("upn"); +} + diff --git a/src/ApiService/FunctionalTests/1f-api/WebhookLogs.cs b/src/ApiService/FunctionalTests/1f-api/WebhookLogs.cs new file mode 100644 index 0000000000..19997d9e0f --- /dev/null +++ b/src/ApiService/FunctionalTests/1f-api/WebhookLogs.cs @@ -0,0 +1,4 @@ +namespace FunctionalTests { + public class WebhookLogs { + } +} diff --git a/src/ApiService/FunctionalTests/1f-api/WebhookPing.cs b/src/ApiService/FunctionalTests/1f-api/WebhookPing.cs new file mode 100644 index 0000000000..fec948de28 --- /dev/null +++ b/src/ApiService/FunctionalTests/1f-api/WebhookPing.cs @@ -0,0 +1,4 @@ +namespace FunctionalTests { + public class WebhookPing { + } +} diff --git a/src/ApiService/FunctionalTests/1f-api/Webhooks.cs b/src/ApiService/FunctionalTests/1f-api/Webhooks.cs new file mode 100644 index 0000000000..7078f67502 --- /dev/null +++ b/src/ApiService/FunctionalTests/1f-api/Webhooks.cs @@ -0,0 +1,4 @@ +namespace FunctionalTests { + public class Webhooks { + } +} diff --git a/src/ApiService/FunctionalTests/FunctionalTests.csproj b/src/ApiService/FunctionalTests/FunctionalTests.csproj index 650a5f5460..095a81b8e7 100644 --- a/src/ApiService/FunctionalTests/FunctionalTests.csproj +++ b/src/ApiService/FunctionalTests/FunctionalTests.csproj @@ -13,6 +13,7 @@ + diff --git a/src/ApiService/FunctionalTests/1f-api/Helpers.cs b/src/ApiService/FunctionalTests/Helpers.cs similarity index 99% rename from src/ApiService/FunctionalTests/1f-api/Helpers.cs rename to src/ApiService/FunctionalTests/Helpers.cs index a899c13cd0..caf3e8bdfa 100644 --- a/src/ApiService/FunctionalTests/1f-api/Helpers.cs +++ b/src/ApiService/FunctionalTests/Helpers.cs @@ -2,7 +2,6 @@ namespace FunctionalTests { public class Helpers { - public static async Task<(Pool, Scaleset)> CreatePoolAndScaleset(PoolApi poolApi, ScalesetApi scalesetApi, string os = "linux", string? region = null, int numNodes = 2) { var image = (os == "linux") ? ScalesetApi.Image_Ubuntu_20_04 : ScalesetApi.ImageWindows; diff --git a/src/ApiService/FunctionalTests/TestContainer.cs b/src/ApiService/FunctionalTests/TestContainer.cs new file mode 100644 index 0000000000..5d0bcd2ad2 --- /dev/null +++ b/src/ApiService/FunctionalTests/TestContainer.cs @@ -0,0 +1,55 @@ +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; + + +namespace FunctionalTests { + [Trait("Category", "Live")] + public class TestContainer { + + private readonly ITestOutputHelper _output; + ContainerApi _containerApi; + DownloadApi _downloadApi; + public TestContainer(ITestOutputHelper output) { + _output = output; + _containerApi = new ContainerApi(ApiClient.Endpoint, ApiClient.Request, output); + _downloadApi = new DownloadApi(ApiClient.Endpoint, ApiClient.Request, output); + } + + [Fact] + public async Task DownloadNonExistentContainer() { + var r1 = await _downloadApi.Get(); + r1.IsOk!.Should().BeFalse(); + r1.ErrorV!.Item2!.ShouldBeProvided("container").Should().BeTrue(); + + var r2 = await _downloadApi.Get(container: Guid.NewGuid().ToString()); + r2.IsOk!.Should().BeFalse(); + r2.ErrorV!.Item2!.ShouldBeProvided("filename").Should().BeTrue(); + + var r3 = await _downloadApi.Get(filename: Guid.NewGuid().ToString()); + r3.IsOk!.Should().BeFalse(); + r3.ErrorV!.Item2!.ShouldBeProvided("container").Should().BeTrue(); + + var r4 = await _downloadApi.Get(container: Guid.NewGuid().ToString(), filename: Guid.NewGuid().ToString()); + r4.IsOk!.Should().BeFalse(); + r4.ErrorV!.Item1.Should().Be(System.Net.HttpStatusCode.InternalServerError); + } + + [Fact] + public async Task CreateGetDeleteContainer() { + var containerName = Guid.NewGuid().ToString(); + var container = await _containerApi.Post(containerName); + container.IsOk.Should().BeTrue($"failed to create container due to {container.ErrorV}"); + + var c = await _containerApi.Get(containerName); + + + var d = await _containerApi.Delete(containerName); + + return; + + } + + + } +} diff --git a/src/ApiService/FunctionalTests/TestInfo.cs b/src/ApiService/FunctionalTests/TestInfo.cs new file mode 100644 index 0000000000..fa3b11e815 --- /dev/null +++ b/src/ApiService/FunctionalTests/TestInfo.cs @@ -0,0 +1,23 @@ +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; + + +namespace FunctionalTests { + [Trait("Category", "Live")] + public class TestInfo { + private readonly ITestOutputHelper _output; + InfoApi _infoApi; + public TestInfo(ITestOutputHelper output) { + _output = output; + _infoApi = new InfoApi(ApiClient.Endpoint, ApiClient.Request, output); + } + + [Fact] + async Task GetInfo() { + var info = await _infoApi.Get(); + info.IsOk.Should().BeTrue(); + info.OkV!.Versions.ContainsKey("onefuzz").Should().BeTrue(); + } + } +} diff --git a/src/ApiService/FunctionalTests/TestNode.cs b/src/ApiService/FunctionalTests/TestNode.cs index 781996b54a..79308a1ab9 100644 --- a/src/ApiService/FunctionalTests/TestNode.cs +++ b/src/ApiService/FunctionalTests/TestNode.cs @@ -1,4 +1,5 @@ -using Xunit; +using FluentAssertions; +using Xunit; using Xunit.Abstractions; namespace FunctionalTests { @@ -23,47 +24,52 @@ public TestNode(ITestOutputHelper output) { async Task GetNonExistentNode() { var n = await _nodeApi.Get(Guid.NewGuid()); - Assert.False(n.IsOk); - Assert.True(n.ErrorV!.UnableToFindNode); + n.IsOk.Should().BeFalse(); + n.ErrorV!.UnableToFindNode.Should().BeTrue(); } [Fact] async Task GetAllNodes() { var ns = await _nodeApi.Get(); - - Assert.True(ns.IsOk, $"Failed to get all nodes due to {ns.ErrorV}"); + ns.IsOk.Should().BeTrue("failed to get all nodes due to {0}", ns.ErrorV); foreach (var n in ns.OkV!) { _output.WriteLine($"node machine id: {n.MachineId}, scaleset id: {n.ScalesetId}, poolName: {n.PoolName}, poolId: {n.PoolId} state: {n.State}, version: {n.Version}"); } } + [Fact] + async Task DeleteNonExistentNode() { + var n = await _nodeApi.Delete(Guid.NewGuid()); + n.IsError.Should().BeTrue(); + n.Error!.UnableToFindNode.Should().BeTrue(); + } + [Fact] async Task GetPatchPostDelete() { var (pool, scaleset) = await Helpers.CreatePoolAndScaleset(_poolApi, _scalesetApi, "linux"); scaleset = await _scalesetApi.WaitWhile(scaleset.ScalesetId, sc => sc.State == "init" || sc.State == "setup"); - Assert.True(scaleset.Nodes!.Count > 0); + scaleset.Nodes!.Should().NotBeEmpty(); var nodeState = scaleset.Nodes!.First(); var nodeResult = await _nodeApi.Get(nodeState.MachineId); - Assert.True(nodeResult.IsOk, $"failed to get node due to {nodeResult.ErrorV}"); + nodeResult.IsOk.Should().BeTrue("failed to get node due to {0}", nodeResult.ErrorV); var node = nodeResult.OkV!.First(); node = await _nodeApi.WaitWhile(node.MachineId, n => n.State == "init" || n.State == "setup"); var r = await _nodeApi.Patch(node.MachineId); - Assert.True(r.Result); + r.Result.Should().BeTrue(); var rr = await _nodeApi.Update(node.MachineId, false); var d = await _nodeApi.Delete(node.MachineId); - Assert.True(d.Result); + d.Result.Should().BeTrue(); var deletePool = await _poolApi.Delete(pool.Name); - Assert.True(deletePool.Result); + deletePool.Result.Should().BeTrue(); } - } } diff --git a/src/ApiService/FunctionalTests/TestPool.cs b/src/ApiService/FunctionalTests/TestPool.cs index 01562a3e1b..03cf55f5e7 100644 --- a/src/ApiService/FunctionalTests/TestPool.cs +++ b/src/ApiService/FunctionalTests/TestPool.cs @@ -1,4 +1,5 @@ -using Xunit; +using FluentAssertions; +using Xunit; using Xunit.Abstractions; namespace FunctionalTests { @@ -20,7 +21,7 @@ public TestPool(ITestOutputHelper output) { [Fact] async Task GetNonExistentPool() { var p = await _poolApi.Get(name: Guid.NewGuid().ToString()); - Assert.True(p.ErrorV!.UnableToFindPoolError); + p.ErrorV!.UnableToFindPoolError.Should().BeTrue("{0}", p.ErrorV!); } @@ -33,7 +34,7 @@ public async Task DeleteFunctionalTestPools() { [Fact] public async Task GetPools() { var pools = await _poolApi.Get(); - Assert.True(pools.IsOk); + pools.IsOk.Should().BeTrue(); if (!pools.OkV!.Any()) { _output.WriteLine("Got empty pools"); @@ -53,16 +54,16 @@ public async Task CreateAndDelete() { _output.WriteLine($"creating pool {newPoolName}"); var newPool = await _poolApi.Create(newPoolName, "linux"); - Assert.True(newPool.IsOk, $"failed to create new pool: {newPool.ErrorV}"); + newPool.IsOk.Should().BeTrue("failed to create new pool: {0}", newPool.ErrorV); var poolsCreated = await _poolApi.Get(); - Assert.True(poolsCreated.IsOk, $"failed to get pools: {poolsCreated.ErrorV}"); + poolsCreated.IsOk.Should().BeTrue("failed to get pools: {0}", poolsCreated.ErrorV); var newPools = poolsCreated.OkV!.Where(p => p.Name == newPoolName); - Assert.True(newPools.Count() == 1); + newPools.Count().Should().Be(1); var deletedPoolResult = await _poolApi.Delete(newPoolName); - Assert.True(deletedPoolResult.Result); + deletedPoolResult.Result.Should().BeTrue(); } } } diff --git a/src/ApiService/FunctionalTests/TestProxy.cs b/src/ApiService/FunctionalTests/TestProxy.cs index e24a6f7ce2..7ecbb30fba 100644 --- a/src/ApiService/FunctionalTests/TestProxy.cs +++ b/src/ApiService/FunctionalTests/TestProxy.cs @@ -1,4 +1,5 @@ -using Xunit; +using FluentAssertions; +using Xunit; using Xunit.Abstractions; namespace FunctionalTests { @@ -24,7 +25,8 @@ public TestProxy(ITestOutputHelper output) { [Fact] public async Task GetProxies() { var allProxiesResult = await _proxyApi.Get(); - Assert.True(allProxiesResult.IsOk, $"failed to get proxies due to {allProxiesResult.ErrorV}"); + + allProxiesResult.IsOk.Should().BeTrue("failed to get proxies due to {0}", allProxiesResult.ErrorV); if (!allProxiesResult.OkV!.Any()) { _output.WriteLine("Got empty list of proxies"); @@ -35,18 +37,18 @@ public async Task GetProxies() { } } - [Fact] + //TODO: do not run this for now - this triggers: https://github.com/microsoft/onefuzz/issues/2331 + //[Fact] public async Task CreateResetDelete() { var (newPool, newScaleset) = await Helpers.CreatePoolAndScaleset(_poolApi, _scalesetApi, "linux"); newScaleset = await _scalesetApi.WaitWhile(newScaleset.ScalesetId, sc => sc.State == "init" || sc.State == "setup"); + newScaleset.Nodes!.Should().NotBeEmpty(); - Assert.True(newScaleset.Nodes!.Count > 0); - - var firstNode = newScaleset.Nodes.First(); + var firstNode = newScaleset.Nodes!.First(); var nodeResult = await _nodeApi.Get(machineId: firstNode.MachineId); - Assert.True(nodeResult.IsOk); + nodeResult.IsOk.Should().BeTrue(); var node = nodeResult.OkV!.First(); node = await _nodeApi.WaitWhile(node.MachineId, n => n.State == "init" || n.State == "setup"); @@ -55,26 +57,23 @@ public async Task CreateResetDelete() { var proxyAgain = await _proxyApi.Create(newScaleset.ScalesetId, node.MachineId, 2223, 1); - Assert.True(proxy.IsOk, $"failed to create proxy due to {proxy.ErrorV}"); - Assert.True(proxyAgain.IsOk, $"failed to create proxy with same config due to {proxyAgain.ErrorV}"); - - Assert.Equal(proxy.OkV!, proxyAgain.OkV!); + proxy.IsOk.Should().BeTrue("failed to create proxy due to {0}", proxy.ErrorV); + proxyAgain.IsOk.Should().BeTrue("failed to create proxy with same config due to {0}", proxyAgain.ErrorV); + proxy.OkV!.Should().BeEquivalentTo(proxyAgain.OkV!); _output.WriteLine($"created proxy dst ip: {proxy.OkV!.Forward.DstIp}, srcPort: {proxy.OkV.Forward.SrcPort} dstport: {proxy.OkV!.Forward.DstPort}, ip: {proxy.OkV!.Ip}"); var proxyReset = await _proxyApi.Reset(newScaleset.Region); - Assert.True(proxyReset.Result); + proxyReset.Result.Should().BeTrue(); var deleteProxy = await _proxyApi.Delete(newScaleset.ScalesetId, node.MachineId); - Assert.True(deleteProxy.Result); - + deleteProxy.Result.Should().BeTrue(); _output.WriteLine($"deleted proxy"); var deletePool = await _poolApi.Delete(newPool.Name); - Assert.True(deletePool.Result); - + deletePool.Result.Should().BeTrue(); _output.WriteLine($"deleted pool {newPool.Name}"); } } diff --git a/src/ApiService/FunctionalTests/TestScaleset.cs b/src/ApiService/FunctionalTests/TestScaleset.cs index fd700798f7..f1a1603011 100644 --- a/src/ApiService/FunctionalTests/TestScaleset.cs +++ b/src/ApiService/FunctionalTests/TestScaleset.cs @@ -1,4 +1,5 @@ -using Xunit; +using FluentAssertions; +using Xunit; using Xunit.Abstractions; namespace FunctionalTests { @@ -20,11 +21,10 @@ public TestScaleset(ITestOutputHelper output) { [Fact] public async Task GetScalesets() { var scalesets = await _scalesetApi.Get(); - Assert.True(scalesets.IsOk, $"failed to get scalesets due to {scalesets.ErrorV}"); + scalesets.IsOk.Should().BeTrue("failed to get scalesets due to {0}", scalesets.ErrorV); if (!scalesets.OkV!.Any()) { _output.WriteLine("Got empty scalesets"); } else { - foreach (var sc in scalesets.OkV!) { if (sc.Error is not null) _output.WriteLine($"Pool: {sc.PoolName} Scaleset: {sc.ScalesetId}, state; {sc.State}, error: {sc.Error}"); @@ -44,16 +44,16 @@ public async Task CreateAndDelete(string os) { _output.WriteLine($"New scale set info id: {newScaleset.ScalesetId}, pool: {newScaleset.PoolName}, state: {newScaleset.State}, error: {newScaleset.Error}"); var scalesetsCreated = await _scalesetApi.Get(); - Assert.True(scalesetsCreated.IsOk, $"failed to get scalesets: {scalesetsCreated.ErrorV}"); + scalesetsCreated.IsOk.Should().BeTrue("failed to get scalesets: {0}", scalesetsCreated.ErrorV); var poolsCreated = await _poolApi.Get(); - Assert.True(poolsCreated.IsOk, $"failed to get pools: {poolsCreated.ErrorV}"); + poolsCreated.IsOk.Should().BeTrue("failed to get pools: {0}", poolsCreated.ErrorV); var newPools = poolsCreated.OkV!.Where(p => p.Name == newPool.Name); var newScalesets = scalesetsCreated.OkV!.Where(sc => sc.ScalesetId == newScaleset.ScalesetId); - Assert.True(newPools.Count() == 1); - Assert.True(newScalesets.Count() == 1); + newPools.Count().Should().Be(1); + newScalesets.Count().Should().Be(1); Console.WriteLine($"Waiting for scaleset to move out from Init State"); newScaleset = await _scalesetApi.WaitWhile(newScaleset.ScalesetId, sc => sc.State == "init" || sc.State == "setup"); @@ -67,8 +67,8 @@ public async Task CreateAndDelete(string os) { } var patch0 = await _scalesetApi.Patch(newScaleset.ScalesetId, 0); - Assert.False(patch0.IsOk); - Assert.True(patch0.ErrorV!.IsWrongSizeError); + patch0.IsOk.Should().BeFalse(); + patch0.ErrorV!.IsWrongSizeError.Should().BeTrue(); // https://github.com/microsoft/onefuzz/issues/2311 //var patch1 = await _scalesetApi.Patch(newScaleset.ScalesetId, 1); //Assert.True(patch1.IsOk, $"scaleset patch failed due to: {patch1}"); @@ -89,9 +89,9 @@ public async Task CreateAndDelete(string os) { var preDeleteScalesets = await _scalesetApi.Get(); var deletedPoolResult = await _poolApi.Delete(newPool.Name); - Assert.True(preDeleteScalesets.IsOk, $"failed to get pre-deleted scalesets due to: {preDeleteScalesets.ErrorV}"); + preDeleteScalesets.IsOk.Should().BeTrue("failed to get pre-deleted scalesets due to: {0}", preDeleteScalesets.ErrorV); var preDelete = preDeleteScalesets.OkV!.Where(sc => sc.PoolName == newPool.Name); - Assert.True(preDelete.Count() == 3); + preDelete.Count().Should().Be(3); Result, Error> deletedPool; do { @@ -99,17 +99,18 @@ public async Task CreateAndDelete(string os) { deletedPool = await _poolApi.Get(newPool.Name); } while (deletedPool.IsOk); - Assert.True(deletedPool.ErrorV!.UnableToFindPoolError); + + deletedPool.ErrorV!.UnableToFindPoolError.Should().BeTrue(); var postDeleteScalesets = await _scalesetApi.Get(); - Assert.True(postDeleteScalesets.IsOk, $"failed to get scalesets after finishing pool deletion due to {postDeleteScalesets.ErrorV}"); + postDeleteScalesets.IsOk.Should().BeTrue("failed to get scalesets after finishing pool deletion due to {0}", postDeleteScalesets.ErrorV); _output.WriteLine($"Pool is deleted {newPool.Name}"); var postDelete = postDeleteScalesets.OkV!.Where(sc => sc.PoolName == newPool.Name); - Assert.False(postDelete.Any()); + postDelete.Should().BeEmpty(); var patch1 = await _scalesetApi.Patch(newScaleset.ScalesetId, 1); - Assert.False(patch1.IsOk); - Assert.True(patch1.ErrorV!.UnableToFindScalesetError); + patch1.IsOk.Should().BeFalse(); + patch1.ErrorV!.UnableToFindScalesetError.Should().BeTrue(); } return; diff --git a/src/ApiService/FunctionalTests/TestTasks.cs b/src/ApiService/FunctionalTests/TestTasks.cs new file mode 100644 index 0000000000..a0ea1ff747 --- /dev/null +++ b/src/ApiService/FunctionalTests/TestTasks.cs @@ -0,0 +1,32 @@ +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; + + +namespace FunctionalTests { + [Trait("Category", "Live")] + public class TestTasks { + TaskApi _taskApi; + + private readonly ITestOutputHelper _output; + + public TestTasks(ITestOutputHelper output) { + this._output = output; + _taskApi = new TaskApi(ApiClient.Endpoint, ApiClient.Request, output); + } + + [Fact] + public async Task GetNonExistentTask() { + var t1 = await _taskApi.Get(Guid.NewGuid()); + t1.IsOk.Should().BeTrue(); + t1.OkV.Should().BeEmpty(); + + + var t2 = await _taskApi.Get(Guid.NewGuid(), Guid.NewGuid()); + t2.IsOk.Should().BeFalse(); + t2.ErrorV!.UnableToFindTask.Should().BeTrue(); + } + + + } +} diff --git a/src/ApiService/FunctionalTests/packages.lock.json b/src/ApiService/FunctionalTests/packages.lock.json index 0877e637e0..844c073610 100644 --- a/src/ApiService/FunctionalTests/packages.lock.json +++ b/src/ApiService/FunctionalTests/packages.lock.json @@ -8,6 +8,15 @@ "resolved": "3.1.2", "contentHash": "wuLDIDKD5XMt0A7lE31JPenT7QQwZPFkP5rRpdJeblyXZ9MGLI8rYjvm5fvAKln+2/X+4IxxQDxBtwdrqKNLZw==" }, + "FluentAssertions": { + "type": "Direct", + "requested": "[6.7.0, )", + "resolved": "6.7.0", + "contentHash": "PWbow/R3MnYDP8UW7zh/w80rGb+1NufGoNJeuzouTo2bqpvwNTFxbDwF6XWfFZ5IuquL2225Um+qSyZ8jVsT+w==", + "dependencies": { + "System.Configuration.ConfigurationManager": "4.4.0" + } + }, "Microsoft.Identity.Client": { "type": "Direct", "requested": "[4.46.1, )", @@ -354,6 +363,14 @@ "System.Threading.Tasks": "4.3.0" } }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "gWwQv/Ug1qWJmHCmN17nAbxJYmQBM/E94QxKLksvUiiKB1Ld3Sc/eK1lgmbSjDFxkQhVuayI/cGFZhpBSodLrg==", + "dependencies": { + "System.Security.Cryptography.ProtectedData": "4.4.0" + } + }, "System.Console": { "type": "Transitive", "resolved": "4.3.0", @@ -922,6 +939,11 @@ "System.Threading.Tasks": "4.3.0" } }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "cJV7ScGW7EhatRsjehfvvYVBvtiSMKgN8bOVI0bQhnF5bU7vnHVIsH49Kva7i7GWaWYvmEzkYVk1TC+gZYBEog==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", diff --git a/src/ApiService/IntegrationTests/IntegrationTests.csproj b/src/ApiService/IntegrationTests/IntegrationTests.csproj index e6d6993552..ac62f4affa 100644 --- a/src/ApiService/IntegrationTests/IntegrationTests.csproj +++ b/src/ApiService/IntegrationTests/IntegrationTests.csproj @@ -5,6 +5,7 @@ + diff --git a/src/ApiService/IntegrationTests/packages.lock.json b/src/ApiService/IntegrationTests/packages.lock.json index 755a57cfdb..2d61f054f3 100644 --- a/src/ApiService/IntegrationTests/packages.lock.json +++ b/src/ApiService/IntegrationTests/packages.lock.json @@ -8,6 +8,15 @@ "resolved": "3.1.2", "contentHash": "wuLDIDKD5XMt0A7lE31JPenT7QQwZPFkP5rRpdJeblyXZ9MGLI8rYjvm5fvAKln+2/X+4IxxQDxBtwdrqKNLZw==" }, + "FluentAssertions": { + "type": "Direct", + "requested": "[6.7.0, )", + "resolved": "6.7.0", + "contentHash": "PWbow/R3MnYDP8UW7zh/w80rGb+1NufGoNJeuzouTo2bqpvwNTFxbDwF6XWfFZ5IuquL2225Um+qSyZ8jVsT+w==", + "dependencies": { + "System.Configuration.ConfigurationManager": "4.4.0" + } + }, "Microsoft.NET.Test.Sdk": { "type": "Direct", "requested": "[17.1.0, )", @@ -1214,6 +1223,14 @@ "System.Threading": "4.3.0" } }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "gWwQv/Ug1qWJmHCmN17nAbxJYmQBM/E94QxKLksvUiiKB1Ld3Sc/eK1lgmbSjDFxkQhVuayI/cGFZhpBSodLrg==", + "dependencies": { + "System.Security.Cryptography.ProtectedData": "4.4.0" + } + }, "System.Console": { "type": "Transitive", "resolved": "4.3.0", @@ -2160,39 +2177,39 @@ "apiservice": { "type": "Project", "dependencies": { - "Azure.Core": "1.25.0", - "Azure.Data.Tables": "12.5.0", - "Azure.Identity": "1.6.0", - "Azure.Messaging.EventGrid": "4.10.0", - "Azure.ResourceManager": "1.3.1", - "Azure.ResourceManager.Compute": "1.0.0-beta.8", - "Azure.ResourceManager.Monitor": "1.0.0-beta.2", - "Azure.ResourceManager.Network": "1.0.0", - "Azure.ResourceManager.Resources": "1.3.0", - "Azure.ResourceManager.Storage": "1.0.0-beta.11", - "Azure.Security.KeyVault.Secrets": "4.3.0", - "Azure.Storage.Blobs": "12.13.0", - "Azure.Storage.Queues": "12.11.0", - "Faithlife.Utility": "0.12.2", - "Microsoft.ApplicationInsights.DependencyCollector": "2.21.0", - "Microsoft.Azure.Functions.Worker": "1.6.0", - "Microsoft.Azure.Functions.Worker.Extensions.EventGrid": "2.1.0", - "Microsoft.Azure.Functions.Worker.Extensions.Http": "3.0.13", - "Microsoft.Azure.Functions.Worker.Extensions.SignalRService": "1.7.0", - "Microsoft.Azure.Functions.Worker.Extensions.Storage": "5.0.0", - "Microsoft.Azure.Functions.Worker.Extensions.Timer": "4.1.0", - "Microsoft.Azure.Functions.Worker.Sdk": "1.3.0", - "Microsoft.Azure.Management.Monitor": "0.28.0-preview", - "Microsoft.Azure.Management.OperationalInsights": "0.24.0-preview", - "Microsoft.Extensions.Logging.ApplicationInsights": "2.21.0", - "Microsoft.Graph": "4.37.0", - "Microsoft.Identity.Client": "4.46.2", - "Microsoft.Identity.Web.TokenCache": "1.23.1", - "Scriban": "5.5.0", - "Semver": "2.1.0", - "System.IdentityModel.Tokens.Jwt": "6.22.1", - "System.Linq.Async": "6.0.1", - "TaskTupleAwaiter": "2.0.0" + "Azure.Core": "[1.25.0, )", + "Azure.Data.Tables": "[12.5.0, )", + "Azure.Identity": "[1.6.0, )", + "Azure.Messaging.EventGrid": "[4.10.0, )", + "Azure.ResourceManager": "[1.3.1, )", + "Azure.ResourceManager.Compute": "[1.0.0-beta.8, )", + "Azure.ResourceManager.Monitor": "[1.0.0-beta.2, )", + "Azure.ResourceManager.Network": "[1.0.0, )", + "Azure.ResourceManager.Resources": "[1.3.0, )", + "Azure.ResourceManager.Storage": "[1.0.0-beta.11, )", + "Azure.Security.KeyVault.Secrets": "[4.3.0, )", + "Azure.Storage.Blobs": "[12.13.0, )", + "Azure.Storage.Queues": "[12.11.0, )", + "Faithlife.Utility": "[0.12.2, )", + "Microsoft.ApplicationInsights.DependencyCollector": "[2.21.0, )", + "Microsoft.Azure.Functions.Worker": "[1.6.0, )", + "Microsoft.Azure.Functions.Worker.Extensions.EventGrid": "[2.1.0, )", + "Microsoft.Azure.Functions.Worker.Extensions.Http": "[3.0.13, )", + "Microsoft.Azure.Functions.Worker.Extensions.SignalRService": "[1.7.0, )", + "Microsoft.Azure.Functions.Worker.Extensions.Storage": "[5.0.0, )", + "Microsoft.Azure.Functions.Worker.Extensions.Timer": "[4.1.0, )", + "Microsoft.Azure.Functions.Worker.Sdk": "[1.3.0, )", + "Microsoft.Azure.Management.Monitor": "[0.28.0-preview, )", + "Microsoft.Azure.Management.OperationalInsights": "[0.24.0-preview, )", + "Microsoft.Extensions.Logging.ApplicationInsights": "[2.21.0, )", + "Microsoft.Graph": "[4.37.0, )", + "Microsoft.Identity.Client": "[4.46.2, )", + "Microsoft.Identity.Web.TokenCache": "[1.23.1, )", + "Scriban": "[5.5.0, )", + "Semver": "[2.1.0, )", + "System.IdentityModel.Tokens.Jwt": "[6.22.1, )", + "System.Linq.Async": "[6.0.1, )", + "TaskTupleAwaiter": "[2.0.0, )" } } } diff --git a/src/ApiService/Tests/packages.lock.json b/src/ApiService/Tests/packages.lock.json index 979711d41f..587dcd926c 100644 --- a/src/ApiService/Tests/packages.lock.json +++ b/src/ApiService/Tests/packages.lock.json @@ -2304,39 +2304,39 @@ "apiservice": { "type": "Project", "dependencies": { - "Azure.Core": "1.25.0", - "Azure.Data.Tables": "12.5.0", - "Azure.Identity": "1.6.0", - "Azure.Messaging.EventGrid": "4.10.0", - "Azure.ResourceManager": "1.3.1", - "Azure.ResourceManager.Compute": "1.0.0-beta.8", - "Azure.ResourceManager.Monitor": "1.0.0-beta.2", - "Azure.ResourceManager.Network": "1.0.0", - "Azure.ResourceManager.Resources": "1.3.0", - "Azure.ResourceManager.Storage": "1.0.0-beta.11", - "Azure.Security.KeyVault.Secrets": "4.3.0", - "Azure.Storage.Blobs": "12.13.0", - "Azure.Storage.Queues": "12.11.0", - "Faithlife.Utility": "0.12.2", - "Microsoft.ApplicationInsights.DependencyCollector": "2.21.0", - "Microsoft.Azure.Functions.Worker": "1.6.0", - "Microsoft.Azure.Functions.Worker.Extensions.EventGrid": "2.1.0", - "Microsoft.Azure.Functions.Worker.Extensions.Http": "3.0.13", - "Microsoft.Azure.Functions.Worker.Extensions.SignalRService": "1.7.0", - "Microsoft.Azure.Functions.Worker.Extensions.Storage": "5.0.0", - "Microsoft.Azure.Functions.Worker.Extensions.Timer": "4.1.0", - "Microsoft.Azure.Functions.Worker.Sdk": "1.3.0", - "Microsoft.Azure.Management.Monitor": "0.28.0-preview", - "Microsoft.Azure.Management.OperationalInsights": "0.24.0-preview", - "Microsoft.Extensions.Logging.ApplicationInsights": "2.21.0", - "Microsoft.Graph": "4.37.0", - "Microsoft.Identity.Client": "4.46.2", - "Microsoft.Identity.Web.TokenCache": "1.23.1", - "Scriban": "5.5.0", - "Semver": "2.1.0", - "System.IdentityModel.Tokens.Jwt": "6.22.1", - "System.Linq.Async": "6.0.1", - "TaskTupleAwaiter": "2.0.0" + "Azure.Core": "[1.25.0, )", + "Azure.Data.Tables": "[12.5.0, )", + "Azure.Identity": "[1.6.0, )", + "Azure.Messaging.EventGrid": "[4.10.0, )", + "Azure.ResourceManager": "[1.3.1, )", + "Azure.ResourceManager.Compute": "[1.0.0-beta.8, )", + "Azure.ResourceManager.Monitor": "[1.0.0-beta.2, )", + "Azure.ResourceManager.Network": "[1.0.0, )", + "Azure.ResourceManager.Resources": "[1.3.0, )", + "Azure.ResourceManager.Storage": "[1.0.0-beta.11, )", + "Azure.Security.KeyVault.Secrets": "[4.3.0, )", + "Azure.Storage.Blobs": "[12.13.0, )", + "Azure.Storage.Queues": "[12.11.0, )", + "Faithlife.Utility": "[0.12.2, )", + "Microsoft.ApplicationInsights.DependencyCollector": "[2.21.0, )", + "Microsoft.Azure.Functions.Worker": "[1.6.0, )", + "Microsoft.Azure.Functions.Worker.Extensions.EventGrid": "[2.1.0, )", + "Microsoft.Azure.Functions.Worker.Extensions.Http": "[3.0.13, )", + "Microsoft.Azure.Functions.Worker.Extensions.SignalRService": "[1.7.0, )", + "Microsoft.Azure.Functions.Worker.Extensions.Storage": "[5.0.0, )", + "Microsoft.Azure.Functions.Worker.Extensions.Timer": "[4.1.0, )", + "Microsoft.Azure.Functions.Worker.Sdk": "[1.3.0, )", + "Microsoft.Azure.Management.Monitor": "[0.28.0-preview, )", + "Microsoft.Azure.Management.OperationalInsights": "[0.24.0-preview, )", + "Microsoft.Extensions.Logging.ApplicationInsights": "[2.21.0, )", + "Microsoft.Graph": "[4.37.0, )", + "Microsoft.Identity.Client": "[4.46.2, )", + "Microsoft.Identity.Web.TokenCache": "[1.23.1, )", + "Scriban": "[5.5.0, )", + "Semver": "[2.1.0, )", + "System.IdentityModel.Tokens.Jwt": "[6.22.1, )", + "System.Linq.Async": "[6.0.1, )", + "TaskTupleAwaiter": "[2.0.0, )" } } }