Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .apify/test.mock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"Name": "Test Mock",
"Endpoint": "/test",
"Method": "GET",
"Responses": [
{
"Condition": "default",
"StatusCode": 200,
"ResponseTemplate": {
"message": "I'm running out of ideas."
}
}
]
}
4 changes: 2 additions & 2 deletions Apify.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@
<ItemGroup>
<PackageReference Include="Bogus" Version="35.6.3" />
<PackageReference Include="ConsoleTableExt" Version="3.3.0" />
<PackageReference Include="DynamicExpresso.Core" Version="2.19.0" />
<PackageReference Include="Jint" Version="4.4.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
</ItemGroup>

<!-- Copy example files to output directory but exclude from build -->
<ItemGroup>
<EmbeddedResource Include="includes\*" />
<None Update="Examples\*.*" CopyToOutputDirectory="PreserveNewest" />
<Compile Remove="Examples\**" />
<EmbeddedResource Remove="Examples\**" />
Expand All @@ -49,5 +50,4 @@
<ItemGroup>
<Folder Include="bin\Debug\net8.0\" />
</ItemGroup>

</Project>
20 changes: 20 additions & 0 deletions Commands/AboutCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Reflection;
using Apify.Services;
using Apify.Utils;
using Jint;


namespace Apify.Commands
Expand Down Expand Up @@ -31,7 +32,26 @@

private Task ExecuteAsync()
{



// Find your resource name here
var expresso = new DynamicScriptingManager();

// Add a fake window object to the JS global scope



var result = expresso.Compile<int>("faker.number.int({ min: 1, max: 100 })");

Console.WriteLine(result.ToString());



//Console.WriteLine(engine.Evaluate("Config.MockServer").ToString());

return Task.CompletedTask;
ConsoleHelper.WriteHeader("About Apify");

Check warning on line 54 in Commands/AboutCommand.cs

View workflow job for this annotation

GitHub Actions / build-and-test (macos-latest, 8.0.x)

Unreachable code detected

Check warning on line 54 in Commands/AboutCommand.cs

View workflow job for this annotation

GitHub Actions / build-and-test (macos-latest, 8.0.x)

Unreachable code detected

Check warning on line 54 in Commands/AboutCommand.cs

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest, 8.0.x)

Unreachable code detected

Check warning on line 54 in Commands/AboutCommand.cs

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest, 8.0.x)

Unreachable code detected

Check warning on line 54 in Commands/AboutCommand.cs

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest, 8.0.x)

Unreachable code detected

Check warning on line 54 in Commands/AboutCommand.cs

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest, 8.0.x)

Unreachable code detected
Console.WriteLine("A robust and powerful CLI tool for testing APIs and a mock server.");
Console.WriteLine();

Expand Down
2 changes: 1 addition & 1 deletion Commands/CallCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ private async Task ExecuteRunCommand(CallCommandOptions options)


var assertionExecutor = new AssertionExecutor(response, requestSchema);
var testResults = await assertionExecutor.RunAsync(requestSchema.Tests ?? new List<AssertionEntity>());
var testResults = await assertionExecutor.RunAsync(requestSchema.Tests ?? new List<AssertionEntity>(), configService.LoadEnvironment(envName));

apiExecutor.DisplayTestStats(testResults);
apiExecutor.DisplayTestResults(testResults);
Expand Down
11 changes: 5 additions & 6 deletions Commands/CreateMockCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,7 @@ private Task<MockDefinitionSchema> GatherMockApiInformation(CreateMockCommandOpt
// Basic mock API information
name = ConsoleHelper.PromptInput("Mock API name (e.g., Get User)");
endpoint = ConsoleHelper.PromptInput("Endpoint path (e.g., /api/users/1 or /users):");
method = ConsoleHelper.PromptChoice<string>("HTTP method:", ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"
]);
method = ConsoleHelper.PromptChoice<string>("HTTP method:", ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);
}


Expand Down Expand Up @@ -259,10 +258,10 @@ private Task<MockDefinitionSchema> GatherMockApiInformation(CreateMockCommandOpt

while (true)
{
string headerName = ConsoleHelper.PromptInput<string>("Header name (e.g., Cache-Control):", required:false);
string headerName = ConsoleHelper.PromptInput<string>("Header name (e.g., Cache-Control)", required:false);
if (string.IsNullOrWhiteSpace(headerName)) break;

string headerValue = ConsoleHelper.PromptInput($"Value for {headerName}:");
string headerValue = ConsoleHelper.PromptInput($"Value for {headerName}");
headers[headerName] = headerValue;
}
}
Expand All @@ -274,7 +273,7 @@ private Task<MockDefinitionSchema> GatherMockApiInformation(CreateMockCommandOpt
{
while (true)
{
string delayStr = ConsoleHelper.PromptInput("Delay in milliseconds (e.g., 500):");
string delayStr = ConsoleHelper.PromptInput("Delay in milliseconds (e.g., 500)");

if (int.TryParse(delayStr, out var delay) && delay >= 0)
{
Expand Down Expand Up @@ -321,7 +320,7 @@ private int PromptForStatusCode()
{
while (true)
{
int customCode = ConsoleHelper.PromptInput<int>("Enter custom status code (100-599):");
int customCode = ConsoleHelper.PromptInput<int>("Enter custom status code (100-599)");
if (customCode is >= 100 and <= 599)
{
return customCode;
Expand Down
4 changes: 2 additions & 2 deletions Commands/CreateRequestCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class CreateRequestCommand: Command
var urlOption = new Option<string>(
"--url",
() => "",
"URL for the request (e.g., {{baseUrl}}/users/{{userId}} or https://api.example.com/users)"
"URL for the request (e.g., {{env.baseUrl}}/users/{{vars.userId}} or https://api.example.com/users)"
);

var forceOption = new Option<bool>(
Expand Down Expand Up @@ -102,7 +102,7 @@ private async Task ExecuteAsync(string filePath, string name, string method, str
name = ConsoleHelper.PromptInput<string>("API request name (e.g., Get User)");
method = ConsoleHelper.PromptChoice("Choose HTTP Method?", ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"
]);
url = ConsoleHelper.PromptInput<string>("URL (e.g., {{baseUrl}}/users/{{userId}} or https://api.example.com/users)", required: true);
url = ConsoleHelper.PromptInput<string>("URL (e.g., {{env.baseUrl}}/users/{{vars.userId}} or https://api.example.com/users)", required: true);
}


Expand Down
18 changes: 9 additions & 9 deletions Commands/InitCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ private async Task ExecuteAsync(string? name, bool mock, bool force, bool debug)
Tests = [
new AssertionEntity {
Title = "Status code is successful",
Case = "Assert.Response.StatusCodeIs(200)",
Case = "$.assert.equals($.assert.response.getStatusCode(), 200)",
}
]
};
Expand All @@ -246,20 +246,20 @@ private async Task ExecuteAsync(string? name, bool mock, bool force, bool debug)
{
Json = new JObject
{
{ "name", "{{expr|> Faker.Name.FirstName()}}" },
{ "job", "{{expr|> Faker.Name.JobTitle()}}" }
{ "name", "{# $.faker.person.fullName() #}" },
{ "job", "{# $.faker.person.jobTitle() #}" }
}
},
PayloadType = PayloadContentType.Json,
Tests = [
new AssertionEntity {
Title = "Status code is Created",
Case = "Assert.Response.StatusCodeIs(201)",
Case = "$.assert.equals($.response.getStatusCode(), 201)",
},

new AssertionEntity {
Title = "The value of response's name matches the request body",
Case = "Assert.Equals(Request.Body.Json.name, Response.Json.name)",
Case = "$.assert.equals($.request.getBody().json.name, $.response.getJson().name)",
}
]
};
Expand All @@ -276,14 +276,14 @@ private async Task ExecuteAsync(string? name, bool mock, bool force, bool debug)
if (mock)
{

// Create a sample Get User by ID mock definition
// Create a sample Get User by ID mock definition
string getUserByIdMockJson = @"{
""Name"": ""Mock User by ID"",
""Method"": ""GET"",
""Endpoint"": ""/api/users/{id}"",
""Responses"": [
{
""Condition"": ""path[\""id\""] == \""2\"""",
""Condition"": ""$.path.id == 2"",
""StatusCode"": 200,
""Headers"": {
""X-Apify-Version"": ""{{env.apiKey}}""
Expand All @@ -299,8 +299,8 @@ private async Task ExecuteAsync(string? name, bool mock, bool force, bool debug)
""StatusCode"": 200,
""ResponseTemplate"": {
""id"": ""{{path.id}}"",
""name"": ""{{expr|> Faker.Name.FirstName()}} {{expr|> Faker.Name.LastName()}}"",
""email"": ""{{expr|> Faker.Internet.Email()}}""
""name"": ""{# $.faker.person.fullName() #}"",
""email"": ""{# $.faker.internet.email() #}""
}
}
]
Expand Down
36 changes: 21 additions & 15 deletions Commands/MockServerCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,37 @@ public MockServerCommand(): base("server:mock", "Start a mock API server based o
description: "The port on which to run the mock server (on Windows, ports above 1024 may not require admin rights)",
getDefaultValue: () => 0);

var directoryOption = new Option<string>(
name: "--directory",
description: "The directory containing mock definition files",
getDefaultValue: () => ".apify");
var projectDirectoryOption = new Option<string>(
name: "--project",
description: "The project directory containing mock definition files",
getDefaultValue: () => "");

var verboseOption = new Option<bool>(
name: "--verbose",
description: "Show detailed output",
getDefaultValue: () => false);


var watchOption = new Option<bool>(
name: "--watch",
description: "Watch for file changes and reload the server automatically");
watchOption.AddAlias("-w");

AddOption(portOption);
AddOption(directoryOption);
AddOption(projectDirectoryOption);
AddOption(verboseOption);

this.SetHandler(async (port, directory, verbose, debug) =>
AddOption(watchOption);

this.SetHandler(async (port, projectDirectory, verbose, watch, debug) =>
{
await RunMockServerAsync(port, directory, verbose, debug);
}, portOption, directoryOption, verboseOption, RootOption.DebugOption);
await RunMockServerAsync(port, projectDirectory, verbose, watch, debug);
}, portOption, projectDirectoryOption, verboseOption, watchOption, RootOption.DebugOption);

}
private async Task RunMockServerAsync(int port, string directory, bool verbose, bool debug)

private async Task RunMockServerAsync(int port, string projectDirectory, bool verbose, bool watch, bool debug)
{
var mockServer = new MockServerService(directory, debug);
await mockServer.StartAsync(port, verbose);
var mockServer = new MockServerService(projectDirectory, debug);
await mockServer.StartAsync(port, verbose, watch);
}
}
}
2 changes: 1 addition & 1 deletion Commands/TestsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ private async Task RunAllTestsAsync(bool verbose, string directory, string? envN

var assertionExecutor = new AssertionExecutor(response, requestSchema);

var testResults = await assertionExecutor.RunAsync(requestSchema.Tests ?? new List<AssertionEntity>());
var testResults = await assertionExecutor.RunAsync(requestSchema.Tests ?? new List<AssertionEntity>(), configService.LoadEnvironment(envName));
totalTestResults.AddTestResults(requestSchema.Url, testResults);

Console.SetCursorPosition(0, cursorPosition);
Expand Down
19 changes: 19 additions & 0 deletions Models/ResponseDefinitionSchema.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Apify.Models;


public class ResponseDefinitionSchema
{
[JsonProperty("isSuccessful")]
public bool IsSuccessful { get; set; }

[JsonProperty("statusCode")]
public int StatusCode { get; set; }

[JsonProperty("headers")]
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();

[JsonProperty("contentHeaders")]
public Dictionary<string, string> ContentHeaders { get; set; } = new Dictionary<string, string>();

[JsonProperty("contentType")]
public string? ContentType { get; set; }

[JsonProperty("body")]
public string Body { get; set; } = string.Empty;


[JsonProperty("json")]
public JToken? Json { get; set; }

[JsonProperty("responseTimeMs")]
public long ResponseTimeMs { get; set; }

[JsonProperty("errorMessage")]
public string? ErrorMessage { get; set; }

public string GetHeader(string name)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ For other platforms, replace `linux-x64` with your target platform:
- macOS: `osx-x64`
- ARM64: `linux-arm64` or `osx-arm64`

> For documentation, please visit [Apify Documentation](https://apify.dev/docs).
> For documentation, please visit [Apify Documentation](docs/Apify-Documentation.md).

## Development
To contribute to Apify or build it from source, follow these steps:
Expand Down
9 changes: 7 additions & 2 deletions Services/ApiExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public async Task<ResponseDefinitionSchema> ExecuteRequestAsync(RequestDefinitio

// Include the error message in the response body for test assertion context
response.Body = ex.ToString();
//response.Body = $"{{\"error\": \"{ex.Message.Replace("\"", "\\\"")}\", \"exception_type\": \"{ex.GetType().Name}\"}}";
//response.Body = $"{{\"error\": \"{ex.Message.Evaluate("\"", "\\\"")}\", \"exception_type\": \"{ex.GetType().Name}\"}}";
}

return response;
Expand Down Expand Up @@ -295,7 +295,7 @@ public RequestDefinitionSchema ApplyEnvToApiDefinition(RequestDefinitionSchema r
}
}

apiDefContent = StubManager.Replace(apiDefContent, stubReplacor);
apiDefContent = TagInterpolationManager.Evaluate(apiDefContent, stubReplacor);

if (string.IsNullOrEmpty(apiDefContent))
{
Expand Down Expand Up @@ -325,6 +325,11 @@ public void DisplayTestResults(TestResults testResults)

if (_options?.Tests == false)
{
if (_options.Debug)
{
ConsoleHelper.WriteWarning("Tests are not enabled, skipping test results display.");
}

return; // No need to display test results if tests are not enabled
}

Expand Down
Loading
Loading