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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ CHATGPT_APIKEY=your-openai-api-key
CHATGPT_MODEL=gpt-3.5-turbo
CHATGPT_IMAGEAPIURL=https://api.openai.com/v1/images/generations

# InVideo AI Configuration
INVIDEO__APIURL=https://pro-api.invideo.io/v1/generate-video
INVIDEO__APIKEY=your-invideo-api-key

# DigitalOcean Spaces (S3) Configuration
S3__ACCESSKEY=your-digitalocean-spaces-access-key
S3__SECRETKEY=your-digitalocean-spaces-secret-key
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ services:
- ChatGPT__ApiUrl=${CHATGPT_APIURL}
- ChatGPT__Model=${CHATGPT_MODEL}
- ChatGPT__ImageApiUrl=${CHATGPT_IMAGEAPIURL}
- InVideo__ApiUrl=${INVIDEO__APIURL}
- InVideo__ApiKey=${INVIDEO__APIKEY}
- MailerSend__MailSender=${MAILERSEND__MAILSENDER}
- MailerSend__ApiURL=${MAILERSEND__APIURL}
- MailerSend__ApiToken=${MAILERSEND__APITOKEN}
Expand Down
40 changes: 40 additions & 0 deletions zTools.API/Controllers/InVideoController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using zTools.Domain.Services.Interfaces;
using zTools.DTO.InVideo;
using System;
using System.Threading.Tasks;

namespace zTools.API.Controllers
{
[Route("[controller]")]
[ApiController]
public class InVideoController : ControllerBase
{
private readonly IInVideoService _inVideoService;
private readonly ILogger<InVideoController> _logger;

public InVideoController(IInVideoService inVideoService, ILogger<InVideoController> logger)
{
_inVideoService = inVideoService;
_logger = logger;
}

[HttpPost("generateVideo")]
public async Task<ActionResult<InVideoResponse>> GenerateVideo([FromBody] InVideoRequest request)
{
try
{
_logger.LogInformation("Generating video with InVideo AI");
var response = await _inVideoService.GenerateVideoAsync(request);
_logger.LogInformation("InVideo AI video generated successfully");
return Ok(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error generating video with InVideo AI");
return StatusCode(500, ex.Message);
}
}
}
}
1 change: 1 addition & 0 deletions zTools.API/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public void ConfigureServices(IServiceCollection services)
services.Configure<MailerSendSetting>(Configuration.GetSection("MailerSend"));
services.Configure<S3Setting>(Configuration.GetSection("S3"));
services.Configure<ChatGPTSetting>(Configuration.GetSection("ChatGPT"));
services.Configure<InVideoSetting>(Configuration.GetSection("InVideo"));

Initializer.Configure(services, Configuration.GetConnectionString("NAuthContext"));

Expand Down
4 changes: 4 additions & 0 deletions zTools.API/appsettings.Template.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
"ApiKey": "your-openai-api-key",
"Model": "gpt-3.5-turbo"
},
"InVideo": {
"ApiUrl": "https://pro-api.invideo.io/v1/generate-video",
"ApiKey": "your-invideo-api-key"
},
"S3": {
"AccessKey": "your-digitalocean-spaces-access-key",
"Endpoint": "https://your-space-name.nyc3.digitaloceanspaces.com",
Expand Down
1 change: 1 addition & 0 deletions zTools.Application/Initializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public static void Configure(IServiceCollection services, string connection, boo

services.AddHttpClient<IMailerSendService, MailerSendService>();
services.AddHttpClient<IChatGPTService, ChatGPTService>();
services.AddHttpClient<IInVideoService, InVideoService>();
}
}
}
69 changes: 69 additions & 0 deletions zTools.Domain/Services/InVideoService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using zTools.Domain.Services.Interfaces;
using zTools.DTO.InVideo;
using zTools.DTO.Settings;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace zTools.Domain.Services
{
public class InVideoService : IInVideoService
{
private readonly HttpClient _httpClient;
private readonly IOptions<InVideoSetting> _inVideoSettings;

public InVideoService(HttpClient httpClient, IOptions<InVideoSetting> inVideoSettings)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_inVideoSettings = inVideoSettings ?? throw new ArgumentNullException(nameof(inVideoSettings));
}

public async Task<InVideoResponse> GenerateVideoAsync(string prompt)
{
var request = new InVideoRequest
{
Prompt = prompt
};

return await GenerateVideoAsync(request);
}

public async Task<InVideoResponse> GenerateVideoAsync(InVideoRequest request)
{
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", _inVideoSettings.Value.ApiKey);

var jsonContent = new StringContent(
JsonConvert.SerializeObject(request, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
}),
Encoding.UTF8,
"application/json");

HttpResponseMessage response = await _httpClient.PostAsync(
_inVideoSettings.Value.ApiUrl,
jsonContent);

var responseContent = await response.Content.ReadAsStringAsync();

if (!response.IsSuccessStatusCode)
{
var errorResponse = JsonConvert.DeserializeObject<InVideoErrorResponse>(responseContent);

if (errorResponse?.Error != null && !string.IsNullOrEmpty(errorResponse.Error.Message))
{
throw new InvalidOperationException(errorResponse.Error.Message);
}

throw new InvalidOperationException("Unknown error");
}

return JsonConvert.DeserializeObject<InVideoResponse>(responseContent);
}
}
}
11 changes: 11 additions & 0 deletions zTools.Domain/Services/Interfaces/IInVideoService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using zTools.DTO.InVideo;
using System.Threading.Tasks;

namespace zTools.Domain.Services.Interfaces
{
public interface IInVideoService
{
Task<InVideoResponse> GenerateVideoAsync(string prompt);
Task<InVideoResponse> GenerateVideoAsync(InVideoRequest request);
}
}
228 changes: 228 additions & 0 deletions zTools.Tests/ACL/InVideoClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using Newtonsoft.Json;
using zTools.ACL;
using zTools.DTO.InVideo;
using zTools.DTO.Settings;
using RichardSzalay.MockHttp;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;

namespace zTools.Tests.ACL
{
public class InVideoClientTests
{
private readonly Mock<IOptions<zToolsetting>> _mockSettings;
private readonly zToolsetting _settings;
private readonly MockHttpMessageHandler _mockHttpHandler;
private readonly HttpClient _httpClient;
private readonly Mock<ILogger<InVideoClient>> _mockLogger;

public InVideoClientTests()
{
_settings = new zToolsetting
{
ApiUrl = "https://api.example.com"
};

_mockSettings = new Mock<IOptions<zToolsetting>>();
_mockSettings.Setup(x => x.Value).Returns(_settings);

_mockHttpHandler = new MockHttpMessageHandler();
_httpClient = _mockHttpHandler.ToHttpClient();

_mockLogger = new Mock<ILogger<InVideoClient>>();
}

[Fact]
public void Constructor_WithValidParameters_CreatesInstance()
{
var client = new InVideoClient(_httpClient, _mockSettings.Object, _mockLogger.Object);
Assert.NotNull(client);
}

[Fact]
public async Task GenerateVideoAsync_WithPrompt_ReturnsResponse()
{
// Arrange
var expectedResponse = new InVideoResponse
{
VideoUrl = "https://invideo.io/videos/test-123",
Title = "Test Video",
Description = "A test video"
};
var apiUrl = $"{_settings.ApiUrl}/InVideo/generateVideo";

_mockHttpHandler
.When(HttpMethod.Post, apiUrl)
.Respond("application/json", JsonConvert.SerializeObject(expectedResponse));

var client = new InVideoClient(_httpClient, _mockSettings.Object, _mockLogger.Object);

// Act
var result = await client.GenerateVideoAsync("Create a test video");

// Assert
Assert.NotNull(result);
Assert.Equal(expectedResponse.VideoUrl, result.VideoUrl);
Assert.Equal(expectedResponse.Title, result.Title);
Assert.Equal(expectedResponse.Description, result.Description);
}

[Fact]
public async Task GenerateVideoAsync_WithRequest_ReturnsResponse()
{
// Arrange
var request = new InVideoRequest
{
Prompt = "Create a video about technology",
Duration = 5.0,
Platform = "youtube_shorts",
Voice = "male",
Accent = "american"
};

var expectedResponse = new InVideoResponse
{
VideoUrl = "https://invideo.io/videos/tech-456",
Title = "Technology Video",
Description = "A video about technology"
};
var apiUrl = $"{_settings.ApiUrl}/InVideo/generateVideo";

_mockHttpHandler
.When(HttpMethod.Post, apiUrl)
.Respond("application/json", JsonConvert.SerializeObject(expectedResponse));

var client = new InVideoClient(_httpClient, _mockSettings.Object, _mockLogger.Object);

// Act
var result = await client.GenerateVideoAsync(request);

// Assert
Assert.NotNull(result);
Assert.Equal(expectedResponse.VideoUrl, result.VideoUrl);
Assert.Equal(expectedResponse.Title, result.Title);
}

[Fact]
public async Task GenerateVideoAsync_MakesCorrectHttpPostRequest()
{
// Arrange
var expectedUrl = $"{_settings.ApiUrl}/InVideo/generateVideo";
var response = new InVideoResponse
{
VideoUrl = "https://invideo.io/videos/test",
Title = "Test",
Description = "Test"
};

_mockHttpHandler
.Expect(HttpMethod.Post, expectedUrl)
.Respond("application/json", JsonConvert.SerializeObject(response));

var client = new InVideoClient(_httpClient, _mockSettings.Object, _mockLogger.Object);

// Act
await client.GenerateVideoAsync("test prompt");

// Assert
_mockHttpHandler.VerifyNoOutstandingExpectation();
}

[Fact]
public async Task GenerateVideoAsync_WhenApiReturns404_ThrowsHttpRequestException()
{
// Arrange
var apiUrl = $"{_settings.ApiUrl}/InVideo/generateVideo";

_mockHttpHandler
.When(HttpMethod.Post, apiUrl)
.Respond(HttpStatusCode.NotFound);

var client = new InVideoClient(_httpClient, _mockSettings.Object, _mockLogger.Object);

// Act & Assert
await Assert.ThrowsAsync<HttpRequestException>(() =>
client.GenerateVideoAsync("test"));
}

[Fact]
public async Task GenerateVideoAsync_WhenApiReturns500_ThrowsHttpRequestException()
{
// Arrange
var apiUrl = $"{_settings.ApiUrl}/InVideo/generateVideo";

_mockHttpHandler
.When(HttpMethod.Post, apiUrl)
.Respond(HttpStatusCode.InternalServerError);

var client = new InVideoClient(_httpClient, _mockSettings.Object, _mockLogger.Object);

// Act & Assert
await Assert.ThrowsAsync<HttpRequestException>(() =>
client.GenerateVideoAsync(new InVideoRequest { Prompt = "test" }));
}

[Fact]
public async Task GenerateVideoAsync_UsesCorrectApiUrl()
{
// Arrange
var customSettings = new zToolsetting { ApiUrl = "https://custom-api.com" };
var mockCustomSettings = new Mock<IOptions<zToolsetting>>();
mockCustomSettings.Setup(x => x.Value).Returns(customSettings);

var expectedResponse = new InVideoResponse
{
VideoUrl = "https://invideo.io/videos/custom",
Title = "Custom",
Description = "Custom"
};
var apiUrl = $"{customSettings.ApiUrl}/InVideo/generateVideo";

_mockHttpHandler
.When(HttpMethod.Post, apiUrl)
.Respond("application/json", JsonConvert.SerializeObject(expectedResponse));

var client = new InVideoClient(_httpClient, mockCustomSettings.Object, _mockLogger.Object);

// Act
var result = await client.GenerateVideoAsync("test");

// Assert
Assert.NotNull(result);
}

[Theory]
[InlineData("Create a video about cats")]
[InlineData("Make an ad for shoes")]
[InlineData("Generate an explainer video")]
public async Task GenerateVideoAsync_WithVariousPrompts_ReturnsResponse(string prompt)
{
// Arrange
var expectedResponse = new InVideoResponse
{
VideoUrl = "https://invideo.io/videos/test",
Title = "Video",
Description = "Description"
};
var apiUrl = $"{_settings.ApiUrl}/InVideo/generateVideo";

_mockHttpHandler
.When(HttpMethod.Post, apiUrl)
.Respond("application/json", JsonConvert.SerializeObject(expectedResponse));

var client = new InVideoClient(_httpClient, _mockSettings.Object, _mockLogger.Object);

// Act
var result = await client.GenerateVideoAsync(prompt);

// Assert
Assert.NotNull(result);
Assert.Equal(expectedResponse.VideoUrl, result.VideoUrl);
}
}
}
Loading
Loading