diff --git a/.idea/.idea.resume builder/.idea/dataSources.xml b/.idea/.idea.resume builder/.idea/dataSources.xml
index 733ee45..3512eb9 100644
--- a/.idea/.idea.resume builder/.idea/dataSources.xml
+++ b/.idea/.idea.resume builder/.idea/dataSources.xml
@@ -8,5 +8,12 @@
jdbc:sqlite:$USER_HOME$/AppData/Local/resume.db
$ProjectFileDir$
+
+ sqlite.xerial
+ true
+ org.sqlite.JDBC
+ jdbc:sqlite:$PROJECT_DIR$/TestResumeBuilder/bin/Debug/net8.0/TestResumeBuilder.db
+ $ProjectFileDir$
+
\ No newline at end of file
diff --git a/.run/resume builder add job.run.xml b/.run/resume builder add job.run.xml
index d25d13e..02b7e13 100644
--- a/.run/resume builder add job.run.xml
+++ b/.run/resume builder add job.run.xml
@@ -1,8 +1,8 @@
-
+
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/TestResumeBuilder/AppTestBase.cs b/TestResumeBuilder/AppTestBase.cs
deleted file mode 100644
index 258f406..0000000
--- a/TestResumeBuilder/AppTestBase.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using resume_builder;
-using Spectre.Console.Testing;
-
-namespace TestResumeBuilder
-{
- //todo: find way to pass text to test command app for prompts
- public abstract class AppTestBase
- {
- internal CommandAppTester TestApp;
-
- public AppTestBase()
- {
- TestApp = new CommandAppTester();
- TestApp.Configure(Program.AppConfiguration);
- }
-
- protected CommandAppResult Run(IEnumerable cmdArgs, params string[] args) =>
- TestApp.Run(cmdArgs.Concat(args).ToArray());
-
- protected CommandAppResult Run(string cmd, params string[] args) =>
- Run(args.Prepend(cmd));
-
- protected CommandAppFailure RunAndCatch(IEnumerable cmdArgs, params string[] args)
- where T : Exception => TestApp.RunAndCatch(cmdArgs.Concat(args).ToArray());
- }
-}
\ No newline at end of file
diff --git a/TestResumeBuilder/TestBase.cs b/TestResumeBuilder/TestBase.cs
new file mode 100644
index 0000000..294ddbf
--- /dev/null
+++ b/TestResumeBuilder/TestBase.cs
@@ -0,0 +1,55 @@
+using resume_builder;
+using resume_builder.models;
+using Spectre.Console.Testing;
+
+namespace TestResumeBuilder
+{
+ //todo: find way to pass text to test command app for prompts
+ public abstract class TestBase: IDisposable, IAsyncDisposable
+ {
+ internal CommandAppTester TestApp;
+ internal ResumeContext TestDb;
+
+ public TestBase()
+ {
+ //given
+ TestApp = new CommandAppTester();
+ TestApp.Configure(Program.AppConfiguration);
+ TestDb = new ResumeContext();
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ TestDb.Jobs.RemoveRange(TestDb.Jobs);
+ TestDb.Projects.RemoveRange(TestDb.Projects);
+ TestDb.Profiles.RemoveRange(TestDb.Profiles);
+ TestDb.Companies.RemoveRange(TestDb.Companies);
+ TestDb.Skills.RemoveRange(TestDb.Skills);
+ await TestDb.SaveChangesAsync();
+ await TestDb.DisposeAsync();
+ GC.SuppressFinalize(this);
+ }
+
+ public void Dispose()
+ {
+ TestDb.Jobs.RemoveRange(TestDb.Jobs);
+ TestDb.Projects.RemoveRange(TestDb.Projects);
+ TestDb.Profiles.RemoveRange(TestDb.Profiles);
+ TestDb.Companies.RemoveRange(TestDb.Companies);
+ TestDb.Skills.RemoveRange(TestDb.Skills);
+ TestDb.SaveChanges();
+ TestDb.Dispose();
+
+ GC.SuppressFinalize(this);
+ }
+
+ protected CommandAppResult Run(IEnumerable cmdArgs, params string[] args) =>
+ TestApp.Run(cmdArgs.Concat(args).ToArray());
+
+ protected CommandAppResult Run(string cmd, params string[] args) =>
+ Run(args.Prepend(cmd));
+
+ protected CommandAppFailure RunAndCatch(IEnumerable cmdArgs, params string[] args)
+ where T : Exception => TestApp.RunAndCatch(cmdArgs.Concat(args).ToArray());
+ }
+}
\ No newline at end of file
diff --git a/TestResumeBuilder/TestData.cs b/TestResumeBuilder/TestData.cs
deleted file mode 100644
index 4989518..0000000
--- a/TestResumeBuilder/TestData.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Bogus;
-
-namespace TestResumeBuilder;
-internal class TestData
-{
- static private Faker Faker { get; set; } = new();
- public static TheoryData RandomArgs() => [Faker.Random.WordsArray(2)];
-}
diff --git a/TestResumeBuilder/TestResumeBuilder.csproj b/TestResumeBuilder/TestResumeBuilder.csproj
index e44b9bd..9d27581 100644
--- a/TestResumeBuilder/TestResumeBuilder.csproj
+++ b/TestResumeBuilder/TestResumeBuilder.csproj
@@ -20,6 +20,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
+
all
diff --git a/TestResumeBuilder/commands/InitTest.cs b/TestResumeBuilder/commands/InitTest.cs
index 3dd1d8b..9f8a968 100644
--- a/TestResumeBuilder/commands/InitTest.cs
+++ b/TestResumeBuilder/commands/InitTest.cs
@@ -1,22 +1,27 @@
+using resume_builder.models;
namespace TestResumeBuilder.commands;
-public class InitTest : AppTestBase
+public class InitTest: TestBase
{
[Fact]
public void Init_WithNoArgs_ShouldReturnSuccess()
{
+ //when
var result = TestApp.Run("init");
+ //then
Assert.Equal(0, result.ExitCode);
}
-
- [Theory]
- [MemberData(nameof(TestData.RandomArgs), MemberType = typeof(TestData))]
- public void Init_WithArgs_ShouldReturnSuccess(string[] args)
+ [Fact]
+ public void Init_WithNoArgs_ShouldCreateDb()
{
- Assert.True(true);
- //var result = Run("init", args);
- //Assert.That(result.ExitCode, Is.EqualTo(0));
+ //when
+ var result = TestApp.Run("init");
+ //then
+ Assert.True(File.Exists("TestResumeBuilder.db"));
+ Assert.Equal(0, result.ExitCode);
+ TestDb = new ResumeContext();
+ Assert.True(TestDb.Database.CanConnect());
}
}
\ No newline at end of file
diff --git a/TestResumeBuilder/commands/add/AddJobTest.cs b/TestResumeBuilder/commands/add/AddJobTest.cs
new file mode 100644
index 0000000..c0d6cb4
--- /dev/null
+++ b/TestResumeBuilder/commands/add/AddJobTest.cs
@@ -0,0 +1,132 @@
+using Bogus;
+using resume_builder;
+using resume_builder.cli.commands.add;
+using resume_builder.models;
+
+namespace TestResumeBuilder.commands.add;
+
+public class AddJobTest: TestBase
+{
+ static readonly string[] cmdArgs = ["add", "job"];
+
+ public static readonly TheoryData AddJobData =
+ new()
+ {
+ {
+ "jobTitle", "companyName", new DateOnly(2021, 1, 1), "jobDescription", "experience",
+ new DateOnly(2231, 1, 1)
+ }
+ };
+
+ [Fact(Skip =
+ "hangs forever (at least in vs2022) because it's expecting input in interactive mode which can't be emulated right now with its (spectre) test console")]
+ public void AddJob_WithNoArgs_ShouldFail()
+ {
+ //this hangs forever becuase a prompt is required
+ //and I have no idea how to inject a response to the app
+ //I added a timeout so it sould stop
+ var result = TestApp.Run(cmdArgs);
+ Assert.Equal(ExitCode.Error.ToInt(), result.ExitCode);
+ }
+
+ [Theory]
+ [MemberData(nameof(AddJobTestData.JobTitleAndStartDate), MemberType = typeof(AddJobTestData))]
+ public void AddJob_WithNameAndStartOptions_ShouldSucceed(string jobTitle, string companyName, DateOnly startDate)
+ {
+ //when
+ var result = TestApp.Run([.. cmdArgs, "-t", jobTitle, "-s", startDate.ToString(), "-c", companyName]);
+ var resultSettings = result.Settings as AddJobSettings;
+ //then
+ Assert.Multiple(() =>
+ {
+ Assert.Equal(0, result.ExitCode);
+ Assert.NotNull(resultSettings);
+ Assert.Equal(jobTitle, resultSettings.JobTitle);
+ Assert.Equal(companyName, resultSettings.Company);
+ Assert.Equal(startDate, resultSettings.StartDate);
+ });
+ }
+
+ [Theory]
+ [MemberData(nameof(AddJobTestData.AllOptions), MemberType = typeof(AddJobTestData))]
+ public void AddJob_WithAllOptions_ShouldSucceed(string jobTitle, string companyName, DateOnly startDate,
+ string? jobDescription, string? experience, DateOnly? endDate)
+ {
+ //? job description and experience are non-null because in the run it will be parsed/replaced with an empty string
+ // and users can't enter the null value
+ //which is to say they can choose to enter nothing with empty quotes which this test tests for too
+ //when
+ string[]? descriptionArg = jobDescription == null ? [null] : ["-d", $"{jobDescription}"];
+ string[]? expArg = experience == null ? [null] : ["-x", $"{experience}"];
+ string[]? endArg = endDate == null ? [null] : ["-e", $"{endDate}"];
+ List args =
+ [
+ ..cmdArgs, ..descriptionArg, ..expArg, ..endArg, "-t", jobTitle, "-s", startDate.ToString(), "-c",
+ companyName
+ ];
+ var result = Run(args.Where(x => x != null).ToArray()!);
+ var resultSettings = result.Settings as AddJobSettings;
+ //then
+ Assert.Equal(0, result.ExitCode);
+ Assert.NotNull(resultSettings);
+ Assert.Equal(jobTitle, resultSettings.JobTitle);
+ Assert.Equal(companyName, resultSettings.Company);
+ Assert.Equal(startDate, resultSettings.StartDate);
+ Assert.Equal(jobDescription, resultSettings.JobDescription);
+ Assert.Equal(experience, resultSettings.Experience);
+ Assert.Equal(endDate, resultSettings.EndDate);
+ }
+
+ [Theory]
+ [MemberData(nameof(AddJobData))]
+ [MemberData(nameof(AddJobTestData.AllOptions), MemberType = typeof(AddJobTestData))]
+ public void AddJob_WithAllOptions_ShouldUpdateDB(string jobTitle, string companyName, DateOnly startDate,
+ string? jobDescription, string? experience, DateOnly? endDate)
+ {
+ string[] descriptionArg = jobDescription == null ? [null] : [$"-d", $"{jobDescription}"];
+ string[] expArg = experience == null ? [null] : [$"-x", $"{experience}"];
+ string[] endArg = endDate == null ? [null] : [$"-e", $"{endDate}"];
+ string?[] args =
+ [
+ ..cmdArgs, "-t", jobTitle, "-s", startDate.ToString(), "-c", companyName, ..descriptionArg, ..expArg,
+ ..endArg
+ ];
+ var result = TestApp.Run(args.Where(x => x != null).ToArray()!);
+ Assert.Equal(0, result.ExitCode);
+ var job = TestDb.Jobs.First(j => j.Title == jobTitle && j.Company == companyName && j.StartDate == startDate);
+ Assert.NotNull(job);
+ //clean inputs; trim and nullify empty strings
+ jobDescription = jobDescription?.Trim();
+ companyName = companyName.Trim();
+ experience = experience?.Trim();
+ jobTitle = jobTitle.Trim();
+
+
+ Assert.Equal(jobTitle, job!.Title);
+ Assert.Equal(companyName, job.Company);
+ Assert.Equal(startDate, job.StartDate);
+ Assert.Equal(jobDescription.IsBlank() ? null : jobDescription, job.Description);
+ Assert.Equal(experience.IsBlank() ? null : experience, job.Experience);
+ Assert.Equal(endDate, job.EndDate);
+ }
+}
+
+internal class AddJobTestData: JobTestData
+{
+ public static TheoryData JobTitleAndStartDate()
+ {
+ var data = new TheoryData();
+ for(var i = TestRepetitions - 1; i >= 0; i--)
+ data.Add(RandomJobTitle(), RandomCompany(), RandomPastDate());
+ return data;
+ }
+
+ public static TheoryData AllOptions()
+ {
+ var data = new TheoryData();
+ for(var i = TestRepetitions - 1; i >= 0; i--)
+ data.Add(RandomJobTitle(), RandomCompany(), RandomPastDate(), WaffleOrNull(), WaffleOrNull(),
+ RandomFutureDate().OrNull(Faker));
+ return data;
+ }
+}
\ No newline at end of file
diff --git a/TestResumeBuilder/data/JobTestData.cs b/TestResumeBuilder/data/JobTestData.cs
new file mode 100644
index 0000000..70af122
--- /dev/null
+++ b/TestResumeBuilder/data/JobTestData.cs
@@ -0,0 +1,19 @@
+using Bogus;
+using resume_builder.models;
+
+namespace TestResumeBuilder;
+
+internal class JobTestData: TestData
+{
+ protected static Faker BogusJob { get; set; } = new Faker()
+ .RuleFor(job => job.Title, RandomJobTitle)
+ .RuleFor(job => job.Description, RandomTextOrNull)
+ .RuleFor(job => job.Experience, RandomTextOrNull)
+ .RuleFor(job => job.Company, RandomCompany)
+ .RuleFor(job => job.StartDate, RandomPastDate)
+ .RuleFor(job => job.EndDate, RandomEndDate);
+
+ public static string RandomJobTitle() => Faker.Name.JobTitle();
+ public static string RandomCompany() => Faker.Company.CompanyName();
+ public static DateOnly? RandomEndDate() => RandomPastDate().OrNull(Faker);
+}
\ No newline at end of file
diff --git a/TestResumeBuilder/data/TestData.cs b/TestResumeBuilder/data/TestData.cs
new file mode 100644
index 0000000..571de14
--- /dev/null
+++ b/TestResumeBuilder/data/TestData.cs
@@ -0,0 +1,38 @@
+using Bogus;
+using WaffleGenerator;
+
+namespace TestResumeBuilder;
+
+internal class TestData
+{
+ private protected const int TestRepetitions = 10;
+
+
+ private static Random Random { get; set; } = new();
+ protected static Faker Faker { get; set; } = new();
+
+ static private protected int MaxRandomYearsAfterToday()
+ {
+ DateOnly.MaxValue.Deconstruct(out var maxYear, out _, out _);
+ DateOnly.FromDateTime(DateTime.Today).Deconstruct(out var todayYear, out _, out _);
+ return maxYear - todayYear;
+ }
+
+ static private protected int MaxRandomYearsBeforeToday()
+ {
+ DateOnly.MinValue.Deconstruct(out var minYear, out _, out _);
+ DateOnly.FromDateTime(DateTime.Today).Deconstruct(out var todayYear, out _, out _);
+ return todayYear - minYear;
+ }
+
+ public static TheoryData