diff --git a/DiscordBot/DiscordBot.csproj b/DiscordBot/DiscordBot.csproj
index c87f489..f80cc67 100644
--- a/DiscordBot/DiscordBot.csproj
+++ b/DiscordBot/DiscordBot.csproj
@@ -15,6 +15,7 @@
full
+ AnyCPU
full
diff --git a/DiscordBotCore/API/Endpoints/ApiEndpointBase.cs b/DiscordBotCore/API/Endpoints/ApiEndpointBase.cs
new file mode 100644
index 0000000..3a372cc
--- /dev/null
+++ b/DiscordBotCore/API/Endpoints/ApiEndpointBase.cs
@@ -0,0 +1,12 @@
+using DiscordBotCore.Online;
+
+namespace DiscordBotCore.API.Endpoints;
+
+public class ApiEndpointBase
+{
+ internal IPluginManager PluginManager { get; }
+ public ApiEndpointBase(IPluginManager pluginManager)
+ {
+ PluginManager = pluginManager;
+ }
+}
\ No newline at end of file
diff --git a/DiscordBotCore/API/Endpoints/ApiManager.cs b/DiscordBotCore/API/Endpoints/ApiManager.cs
index 1d36165..b86d66b 100644
--- a/DiscordBotCore/API/Endpoints/ApiManager.cs
+++ b/DiscordBotCore/API/Endpoints/ApiManager.cs
@@ -24,7 +24,7 @@ public class ApiManager
AddEndpoint(new HomeEndpoint());
AddEndpoint(new PluginListEndpoint());
AddEndpoint(new PluginListInstalledEndpoint());
- AddEndpoint(new PluginInstallEndpoint());
+ AddEndpoint(new PluginInstallEndpoint(Application.CurrentApplication.PluginManager));
AddEndpoint(new SettingsChangeEndpoint());
AddEndpoint(new SettingsGetEndpoint());
diff --git a/DiscordBotCore/API/Endpoints/PluginManagement/PluginInstallEndpoint.cs b/DiscordBotCore/API/Endpoints/PluginManagement/PluginInstallEndpoint.cs
index 9444f66..f5a42b0 100644
--- a/DiscordBotCore/API/Endpoints/PluginManagement/PluginInstallEndpoint.cs
+++ b/DiscordBotCore/API/Endpoints/PluginManagement/PluginInstallEndpoint.cs
@@ -2,28 +2,34 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using DiscordBotCore.Interfaces.API;
+using DiscordBotCore.Online;
using DiscordBotCore.Others;
using DiscordBotCore.Plugin;
namespace DiscordBotCore.API.Endpoints.PluginManagement;
-public class PluginInstallEndpoint : IEndpoint
+public class PluginInstallEndpoint : ApiEndpointBase, IEndpoint
{
+ public PluginInstallEndpoint(IPluginManager pluginManager) : base(pluginManager)
+ {
+ }
+
public string Path => "/api/plugin/install";
public EndpointType HttpMethod => EndpointType.Post;
+
public async Task HandleRequest(string? jsonRequest)
{
Dictionary jsonDict = await JsonManager.ConvertFromJson>(jsonRequest);
string pluginName = jsonDict["pluginName"];
- OnlinePlugin? pluginInfo = await Application.CurrentApplication.PluginManager.GetPluginDataByName(pluginName);
+ OnlinePlugin? pluginInfo = await PluginManager.GetPluginDataByName(pluginName);
if (pluginInfo == null)
{
return ApiResponse.Fail("Plugin not found.");
}
- Application.CurrentApplication.PluginManager.InstallPluginNoProgress(pluginInfo);
+ PluginManager.InstallPluginNoProgress(pluginInfo);
return ApiResponse.Ok();
}
}
diff --git a/DiscordBotCore/Database/SqlDatabase.cs b/DiscordBotCore/Database/SqlDatabase.cs
index 84226ce..740635d 100644
--- a/DiscordBotCore/Database/SqlDatabase.cs
+++ b/DiscordBotCore/Database/SqlDatabase.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Data;
-using System.Data.SQLite;
+using Microsoft.Data.Sqlite;
using System.IO;
using System.Threading.Tasks;
@@ -9,7 +9,7 @@ namespace DiscordBotCore.Database;
public class SqlDatabase
{
- private readonly SQLiteConnection _Connection;
+ private readonly SqliteConnection _Connection;
///
/// Initialize a SQL connection by specifying its private path
@@ -17,10 +17,9 @@ public class SqlDatabase
/// The path to the database (it is starting from ./Data/Resources/)
public SqlDatabase(string fileName)
{
- if (!File.Exists(fileName))
- SQLiteConnection.CreateFile(fileName);
- var connectionString = $"URI=file:{fileName}";
- _Connection = new SQLiteConnection(connectionString);
+ var connectionString = $"Data Source={fileName}";
+ _Connection = new SqliteConnection(connectionString);
+
}
@@ -53,7 +52,7 @@ public class SqlDatabase
query += ")";
- var command = new SQLiteCommand(query, _Connection);
+ var command = new SqliteCommand(query, _Connection);
await command.ExecuteNonQueryAsync();
}
@@ -77,7 +76,7 @@ public class SqlDatabase
query += ")";
- var command = new SQLiteCommand(query, _Connection);
+ var command = new SqliteCommand(query, _Connection);
command.ExecuteNonQuery();
}
@@ -92,7 +91,7 @@ public class SqlDatabase
{
var query = $"DELETE FROM {tableName} WHERE {KeyName} = '{KeyValue}'";
- var command = new SQLiteCommand(query, _Connection);
+ var command = new SqliteCommand(query, _Connection);
await command.ExecuteNonQueryAsync();
}
@@ -107,7 +106,7 @@ public class SqlDatabase
{
var query = $"DELETE FROM {tableName} WHERE {KeyName} = '{KeyValue}'";
- var command = new SQLiteCommand(query, _Connection);
+ var command = new SqliteCommand(query, _Connection);
command.ExecuteNonQuery();
}
@@ -341,7 +340,7 @@ public class SqlDatabase
{
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
- var command = new SQLiteCommand(query, _Connection);
+ var command = new SqliteCommand(query, _Connection);
var answer = await command.ExecuteNonQueryAsync();
return answer;
}
@@ -355,7 +354,7 @@ public class SqlDatabase
{
if (!_Connection.State.HasFlag(ConnectionState.Open))
_Connection.Open();
- var command = new SQLiteCommand(query, _Connection);
+ var command = new SqliteCommand(query, _Connection);
var r = command.ExecuteNonQuery();
return r;
@@ -370,7 +369,7 @@ public class SqlDatabase
{
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
- var command = new SQLiteCommand(query, _Connection);
+ var command = new SqliteCommand(query, _Connection);
var reader = await command.ExecuteReaderAsync();
var values = new object[reader.FieldCount];
@@ -394,7 +393,7 @@ public class SqlDatabase
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
- var command = new SQLiteCommand(query, _Connection);
+ var command = new SqliteCommand(query, _Connection);
foreach (var parameter in parameters)
{
var p = CreateParameter(parameter);
@@ -423,7 +422,7 @@ public class SqlDatabase
{
if (!_Connection.State.HasFlag(ConnectionState.Open))
_Connection.Open();
- var command = new SQLiteCommand(query, _Connection);
+ var command = new SqliteCommand(query, _Connection);
var reader = command.ExecuteReader();
var values = new object[reader.FieldCount];
@@ -445,7 +444,7 @@ public class SqlDatabase
{
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
- var command = new SQLiteCommand(query, _Connection);
+ var command = new SqliteCommand(query, _Connection);
var reader = await command.ExecuteReaderAsync();
var values = new object[reader.FieldCount];
@@ -463,7 +462,7 @@ public class SqlDatabase
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
- var command = new SQLiteCommand(query, _Connection);
+ var command = new SqliteCommand(query, _Connection);
foreach (var parameter in parameters)
{
var p = CreateParameter(parameter);
@@ -493,7 +492,7 @@ public class SqlDatabase
{
if (!_Connection.State.HasFlag(ConnectionState.Open))
_Connection.Open();
- var command = new SQLiteCommand(query, _Connection);
+ var command = new SqliteCommand(query, _Connection);
var reader = command.ExecuteReader();
var values = new object[reader.FieldCount];
@@ -516,7 +515,7 @@ public class SqlDatabase
{
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
- var command = new SQLiteCommand(query, _Connection);
+ var command = new SqliteCommand(query, _Connection);
var reader = await command.ExecuteReaderAsync();
if (!reader.HasRows)
@@ -541,9 +540,10 @@ public class SqlDatabase
/// The name of the parameter
/// The value of the parameter
/// The SQLiteParameter that has the name, value and DBType set according to your inputs
- private static SQLiteParameter? CreateParameter(string name, object value)
+ private static SqliteParameter? CreateParameter(string name, object value)
{
- var parameter = new SQLiteParameter(name);
+ var parameter = new SqliteParameter();
+ parameter.ParameterName = name;
parameter.Value = value;
if (value is string)
@@ -598,7 +598,7 @@ public class SqlDatabase
///
/// The parameter raw inputs. The Key is name and the Value is the value of the parameter
/// The SQLiteParameter that has the name, value and DBType set according to your inputs
- private static SQLiteParameter? CreateParameter(KeyValuePair parameterValues)
+ private static SqliteParameter? CreateParameter(KeyValuePair parameterValues)
{
return CreateParameter(parameterValues.Key, parameterValues.Value);
}
@@ -614,7 +614,7 @@ public class SqlDatabase
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
- var command = new SQLiteCommand(query, _Connection);
+ var command = new SqliteCommand(query, _Connection);
foreach (var parameter in parameters)
{
var p = CreateParameter(parameter);
@@ -638,7 +638,7 @@ public class SqlDatabase
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
- var command = new SQLiteCommand(query, _Connection);
+ var command = new SqliteCommand(query, _Connection);
foreach (var parameter in parameters)
{
var p = CreateParameter(parameter);
@@ -672,7 +672,7 @@ public class SqlDatabase
if (!_Connection.State.HasFlag(ConnectionState.Open))
await _Connection.OpenAsync();
- var command = new SQLiteCommand(query, _Connection);
+ var command = new SqliteCommand(query, _Connection);
foreach (var parameter in parameters)
{
var p = CreateParameter(parameter);
diff --git a/DiscordBotCore/DiscordBotCore.csproj b/DiscordBotCore/DiscordBotCore.csproj
index 67ee68c..c9555dd 100644
--- a/DiscordBotCore/DiscordBotCore.csproj
+++ b/DiscordBotCore/DiscordBotCore.csproj
@@ -4,9 +4,12 @@
enable
Library
+
+ AnyCPU
+
-
+
diff --git a/DiscordBotCore/Online/Helpers/PluginRepository.cs b/DiscordBotCore/Online/Helpers/PluginRepository.cs
index 6b2770c..4101b2c 100644
--- a/DiscordBotCore/Online/Helpers/PluginRepository.cs
+++ b/DiscordBotCore/Online/Helpers/PluginRepository.cs
@@ -7,11 +7,12 @@ using DiscordBotCore.Plugin;
namespace DiscordBotCore.Online.Helpers;
-internal class PluginRepository : IPluginRepository
+public class PluginRepository : IPluginRepository
{
private readonly IPluginRepositoryConfiguration _pluginRepositoryConfiguration;
- private readonly HttpClient _httpClient;
- internal PluginRepository(IPluginRepositoryConfiguration pluginRepositoryConfiguration)
+ public HttpClient _httpClient;
+
+ public PluginRepository(IPluginRepositoryConfiguration pluginRepositoryConfiguration)
{
_pluginRepositoryConfiguration = pluginRepositoryConfiguration;
_httpClient = new HttpClient();
diff --git a/DiscordBotCore/Online/Helpers/PluginRepositoryConfiguration.cs b/DiscordBotCore/Online/Helpers/PluginRepositoryConfiguration.cs
index 4b309dd..3f73858 100644
--- a/DiscordBotCore/Online/Helpers/PluginRepositoryConfiguration.cs
+++ b/DiscordBotCore/Online/Helpers/PluginRepositoryConfiguration.cs
@@ -1,3 +1,4 @@
+using System.Text.Json.Serialization;
using DiscordBotCore.Interfaces.PluginManagement;
namespace DiscordBotCore.Online.Helpers;
@@ -10,6 +11,7 @@ public class PluginRepositoryConfiguration : IPluginRepositoryConfiguration
public string PluginRepositoryLocation { get; }
public string DependenciesRepositoryLocation { get; }
+ [JsonConstructor]
public PluginRepositoryConfiguration(string baseUrl, string pluginRepositoryLocation, string dependenciesRepositoryLocation)
{
BaseUrl = baseUrl;
diff --git a/DiscordBotCore/Online/PluginManager.cs b/DiscordBotCore/Online/PluginManager.cs
index 312cf59..ce14969 100644
--- a/DiscordBotCore/Online/PluginManager.cs
+++ b/DiscordBotCore/Online/PluginManager.cs
@@ -9,7 +9,24 @@ using DiscordBotCore.Plugin;
namespace DiscordBotCore.Online;
-public sealed class PluginManager
+public interface IPluginManager
+{
+ Task> GetPluginsList();
+ Task GetPluginDataByName(string pluginName);
+ Task AppendPluginToDatabase(PluginInfo pluginData);
+ Task> GetInstalledPlugins();
+ Task IsPluginInstalled(string pluginName);
+ Task MarkPluginToUninstall(string pluginName);
+ Task UninstallMarkedPlugins();
+ Task GetDependencyLocation(string dependencyName);
+ Task GetDependencyLocation(string dependencyName, string pluginName);
+ string GenerateDependencyRelativePath(string pluginName, string dependencyPath);
+ Task InstallPluginNoProgress(OnlinePlugin plugin);
+ Task, List>> GatherInstallDataForPlugin(OnlinePlugin plugin);
+ Task SetEnabledStatus(string pluginName, bool status);
+}
+
+public sealed class PluginManager : IPluginManager
{
private static readonly string _LibrariesBaseFolder = "Libraries";
private readonly IPluginRepository _PluginRepository;
diff --git a/SethCoreTests/ApiTests.cs b/SethCoreTests/ApiTests.cs
new file mode 100644
index 0000000..b467d10
--- /dev/null
+++ b/SethCoreTests/ApiTests.cs
@@ -0,0 +1,45 @@
+using DiscordBotCore;
+using DiscordBotCore.API.Endpoints.PluginManagement;
+using DiscordBotCore.Online;
+using DiscordBotCore.Plugin;
+using Moq;
+
+namespace SethCoreTests;
+
+public class PluginInstallEndpointTests
+{
+ private readonly Mock _mockPluginManager;
+ private readonly PluginInstallEndpoint _endpoint;
+
+ public PluginInstallEndpointTests()
+ {
+ _mockPluginManager = new Mock();
+ _endpoint = new PluginInstallEndpoint(_mockPluginManager.Object);
+ }
+
+ [Fact]
+ public async Task HandleRequest_SuccessfulPluginInstallation_ReturnsOk()
+ {
+ var pluginName = "TestPlugin";
+ var pluginInfo = new OnlinePlugin(1, pluginName, "Description", "1.0", "Author", "http://link", 1);
+ _mockPluginManager.Setup(pm => pm.GetPluginDataByName(pluginName)).ReturnsAsync(pluginInfo);
+ _mockPluginManager.Setup(pm => pm.InstallPluginNoProgress(pluginInfo)).Returns(Task.CompletedTask);
+
+ var jsonRequest = $"{{\"pluginName\":\"{pluginName}\"}}";
+ var response = await _endpoint.HandleRequest(jsonRequest);
+
+ Assert.True(response.Success);
+ }
+
+ [Fact]
+ public async Task HandleRequest_PluginNotFound_ReturnsFail()
+ {
+ var pluginName = "NonExistentPlugin";
+ _mockPluginManager.Setup(pm => pm.GetPluginDataByName(pluginName)).ReturnsAsync((OnlinePlugin?)null);
+
+ var jsonRequest = $"{{\"pluginName\":\"{pluginName}\"}}";
+ var response = await _endpoint.HandleRequest(jsonRequest);
+
+ Assert.False(response.Success);
+ }
+}
\ No newline at end of file
diff --git a/SethCoreTests/PluginRepositoryTests.cs b/SethCoreTests/PluginRepositoryTests.cs
new file mode 100644
index 0000000..f08417b
--- /dev/null
+++ b/SethCoreTests/PluginRepositoryTests.cs
@@ -0,0 +1,69 @@
+using System.Net;
+using DiscordBotCore.Interfaces.PluginManagement;
+using DiscordBotCore.Online.Helpers;
+using Moq;
+using Moq.Protected;
+
+namespace SethCoreTests;
+
+public class PluginRepositoryTests
+{
+ private readonly Mock _mockConfig;
+ private readonly Mock _mockHttpMessageHandler;
+ private readonly PluginRepository _pluginRepository;
+
+ public PluginRepositoryTests()
+ {
+ _mockConfig = new Mock();
+ _mockConfig.SetupGet(c => c.BaseUrl).Returns("http://localhost/");
+ _mockConfig.SetupGet(c => c.PluginRepositoryLocation).Returns("api/plugins/");
+ _mockConfig.SetupGet(c => c.DependenciesRepositoryLocation).Returns("api/dependencies/");
+
+ _mockHttpMessageHandler = new Mock();
+ var httpClient = new HttpClient(_mockHttpMessageHandler.Object)
+ {
+ BaseAddress = new System.Uri(_mockConfig.Object.BaseUrl)
+ };
+
+ _pluginRepository = new PluginRepository(_mockConfig.Object)
+ {
+ _httpClient = httpClient
+ };
+ }
+
+ [Fact]
+ public async Task GetAllPlugins_ReturnsListOfPlugins()
+ {
+ var pluginsJson = "[{\"PluginId\":1,\"PluginName\":\"TestPlugin\",\"PluginDescription\":\"Description\",\"LatestVersion\":\"1.0\",\"PluginAuthor\":\"Author\",\"PluginLink\":\"http://link\",\"OperatingSystem\":1}]";
+ _mockHttpMessageHandler.Protected()
+ .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny())
+ .ReturnsAsync(new HttpResponseMessage
+ {
+ StatusCode = HttpStatusCode.OK,
+ Content = new StringContent(pluginsJson)
+ });
+
+ var result = await _pluginRepository.GetAllPlugins();
+
+ Assert.Single(result);
+ Assert.Equal("TestPlugin", result[0].PluginName);
+ }
+
+ [Fact]
+ public async Task GetPluginById_ReturnsPlugin()
+ {
+ var pluginJson = "{\"PluginId\":1,\"PluginName\":\"TestPlugin\",\"PluginDescription\":\"Description\",\"LatestVersion\":\"1.0\",\"PluginAuthor\":\"Author\",\"PluginLink\":\"http://link\",\"OperatingSystem\":1}";
+ _mockHttpMessageHandler.Protected()
+ .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny())
+ .ReturnsAsync(new HttpResponseMessage
+ {
+ StatusCode = HttpStatusCode.OK,
+ Content = new StringContent(pluginJson)
+ });
+
+ var result = await _pluginRepository.GetPluginById(1);
+
+ Assert.NotNull(result);
+ Assert.Equal("TestPlugin", result.PluginName);
+ }
+}
\ No newline at end of file
diff --git a/SethCoreTests/SethCoreTests.csproj b/SethCoreTests/SethCoreTests.csproj
new file mode 100644
index 0000000..a915d1b
--- /dev/null
+++ b/SethCoreTests/SethCoreTests.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SethDiscordBot.sln b/SethDiscordBot.sln
index 0b60efd..0aabc83 100644
--- a/SethDiscordBot.sln
+++ b/SethDiscordBot.sln
@@ -25,6 +25,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CppCompatibilityModule", "M
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotWebUI", "DiscordBotWebUI\DiscordBotWebUI.csproj", "{8683B195-B729-48BB-805A-D44CA98A0BF6}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SethTests", "SethTests", "{9D2F471B-89EE-4F17-B1EA-869069A9A3B8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SethCoreTests", "SethCoreTests\SethCoreTests.csproj", "{AB4BD8D1-7384-4669-9D75-3BBECFA0A96E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -63,6 +67,10 @@ Global
{8683B195-B729-48BB-805A-D44CA98A0BF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8683B195-B729-48BB-805A-D44CA98A0BF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8683B195-B729-48BB-805A-D44CA98A0BF6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AB4BD8D1-7384-4669-9D75-3BBECFA0A96E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AB4BD8D1-7384-4669-9D75-3BBECFA0A96E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AB4BD8D1-7384-4669-9D75-3BBECFA0A96E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AB4BD8D1-7384-4669-9D75-3BBECFA0A96E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -73,6 +81,7 @@ Global
{FCE9743F-7EB4-4639-A080-FCDDFCC7D689} = {5CF9AD7B-6BF0-4035-835F-722F989C01E1}
{F3C61A47-F758-4145-B496-E3ECCF979D89} = {5CF9AD7B-6BF0-4035-835F-722F989C01E1}
{C67908F9-4A55-4DD8-B993-C26C648226F1} = {EA4FA308-7B2C-458E-8485-8747D745DD59}
+ {AB4BD8D1-7384-4669-9D75-3BBECFA0A96E} = {9D2F471B-89EE-4F17-B1EA-869069A9A3B8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3FB3C5DE-ED21-4D2E-ABDD-3A00EE4A2FFF}