Skip to content
Open
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
27 changes: 27 additions & 0 deletions CodingTracker.Tests/CodingTracker.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4"/>
<PackageReference Include="FakeItEasy" Version="8.3.0" />
<PackageReference Include="FluentAssertions" Version="8.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
<PackageReference Include="xunit" Version="2.9.3"/>
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4"/>
</ItemGroup>

<ItemGroup>
<Using Include="Xunit"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CodingTracker.kilozdazolik\CodingTracker.kilozdazolik.csproj" />
</ItemGroup>

</Project>
31 changes: 31 additions & 0 deletions CodingTracker.Tests/ControllerTests/TrackerControllerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using CodingTracker.kilozdazolik.Controllers;
using CodingTracker.kilozdazolik.Models;
using CodingTracker.kilozdazolik.Services;
using FakeItEasy;

namespace CodingTracker.Tests.ControllerTests;

public class TrackerControllerTests
{
private readonly ITrackerService _service;
private readonly TrackerController _controller;

public TrackerControllerTests()
{
_service = A.Fake<ITrackerService>();
_controller = new TrackerController(_service);
}

[Fact]
public void ViewAllSessions_CallsServiceToGetAllSessions()
{
//Arrange
A.CallTo(() => _service.GetAllSession()).Returns(new List<Tracker>());

//Act
_controller.ViewAllSessions();

//Assert
A.CallTo(() => _service.GetAllSession()).MustHaveHappenedOnceExactly();
}
}
71 changes: 71 additions & 0 deletions CodingTracker.Tests/HelperTests/HelperTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using CodingTracker.kilozdazolik;
using FluentAssertions;
using FluentAssertions.Extensions;

namespace CodingTracker.Tests.Helpers;

public class HelperTests
{
private readonly Helper _helper;

public HelperTests()
{
_helper = new Helper();
}

[Fact]
public void ValidateDate_ValidFormat_ReturnsTrue()
{
//Arrange
string validDate = "08/12/2025 14:30:00";
DateTime parsedDate;

//Act
bool result = _helper.ValidateDate(validDate, out parsedDate);

//Assert
result.Should().BeTrue();
}

[Fact]
public void ValidateDate_InvalidFormat_ReturnsFalse()
{
//Arrange
string invalidDate = "2025.12.01";
DateTime parsedDate;

//Act
bool result = _helper.ValidateDate(invalidDate, out parsedDate);

//Assert
result.Should().BeFalse();
}

[Fact]
public void IsSessionDatesValid_ValidFormat_ReturnsTrue()
{
//Arrange
var startDate = 1.March(2025).At(22, 15).AsLocal();
var endDate = 2.March(2025).At(22, 15).AsLocal();

//Act
bool result = _helper.IsSessionDatesValid(startDate,endDate);

//Assert
result.Should().BeTrue();
}

[Fact]
public void IsSessionDatesValid_InvalidFormat_ReturnsFalse()
{
//Arrange
var startDate = 20.March(2025).At(22, 15).AsLocal();
var endDate = 1.March(2025).At(22, 15).AsLocal();

//Act
bool result = _helper.IsSessionDatesValid(startDate,endDate);

//Assert
result.Should().BeFalse();
}
}
51 changes: 51 additions & 0 deletions CodingTracker.Tests/ServiceTests/TrackerServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using CodingTracker.kilozdazolik.Models;
using CodingTracker.kilozdazolik.Services;
using Dapper;
using FluentAssertions;
using FluentAssertions.Extensions;
using Microsoft.Data.Sqlite;

namespace CodingTracker.Tests.Services;

public class TrackerServiceTests : IDisposable
{
private const string TestConnectionString = "Data Source=CodingTrackerTest;Mode=Memory;Cache=Shared";
private readonly SqliteConnection _keepAliveConnection;

public TrackerServiceTests()
{
_keepAliveConnection = new SqliteConnection(TestConnectionString);
_keepAliveConnection.Open();
_keepAliveConnection.Execute(@"
CREATE TABLE IF NOT EXISTS CodingTracker (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
StartTime TEXT,
EndTime TEXT,
Duration TEXT
)");
}

[Fact]
public void InsertSession_StoresData_InMemory()
{
//Arrange
var service = new TrackerService(TestConnectionString);

var startTime = DateTime.Now;
var endTime = startTime.AddHours(5);

//Act
service.InsertSession(startTime, endTime);

//Assert
var result = _keepAliveConnection.QuerySingle<Tracker>("SELECT * FROM CodingTracker");

result.Should().NotBeNull();
result.Duration.Should().NotBe(200.Milliseconds());
}

public void Dispose()
{
_keepAliveConnection.Close();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// <auto-generated/>
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
global using System.Net.Http;
global using System.Threading;
global using System.Threading.Tasks;
global using Xunit;
24 changes: 24 additions & 0 deletions CodingTracker.kilozdazolik/CodingTracker.kilozdazolik.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0" />
<PackageReference Include="Spectre.Console" Version="0.54.1-alpha.0.10" />
</ItemGroup>

<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
173 changes: 173 additions & 0 deletions CodingTracker.kilozdazolik/Controllers/TrackerController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
using System.Diagnostics;
using CodingTracker.kilozdazolik.Services;
using Spectre.Console;
using CodingTracker.kilozdazolik.Models;
namespace CodingTracker.kilozdazolik.Controllers;

public class TrackerController
{
private readonly ITrackerService _trackerService;
private readonly Helper _helper = new();

public TrackerController(ITrackerService trackerService)
{
_trackerService = trackerService;
}

public void StartSession()
{
AnsiConsole.MarkupLine("Press the Enter key to begin/stop:");
Console.ReadLine();
DateTime startTime = DateTime.Now;
Stopwatch sw = Stopwatch.StartNew();

while (!Console.KeyAvailable || Console.ReadKey(true).Key != ConsoleKey.Enter)
{
Console.Clear();
TimeSpan ts = sw.Elapsed;
AnsiConsole.Markup($"Elapsed time: {ts.Hours:D2}:{ts.Minutes:D2}:{ts.Seconds:D2}");
Thread.Sleep(1000);
}

sw.Stop();
DateTime endTime = DateTime.Now;

Console.Clear();
AnsiConsole.MarkupLine($"Final elapsed time: {sw.Elapsed.Hours:D2}:{sw.Elapsed.Minutes:D2}:{sw.Elapsed.Seconds:D2}");

_trackerService.InsertSession(startTime, endTime);

AnsiConsole.MarkupLine("Press Any Key to Continue.");
Console.ReadKey();
}
public void AddSession()
{
bool confirm = false;
while (!confirm)
{
AnsiConsole.MarkupLine("[yellow]Add a new coding session[/]");

var inputStartDate = AnsiConsole.Prompt(
new TextPrompt<string>("Please write the starting date in this format: (dd/MM/yyyy HH:mm:ss)"));
if (!_helper.ValidateDate(inputStartDate, out DateTime startDate))
{
AnsiConsole.MarkupLine("[red]Invalid start date format![/]");
continue;
}

var inputEndDate = AnsiConsole.Prompt(
new TextPrompt<string>("Please write the ending date in this format: (dd/MM/yyyy HH:mm:ss)"));
if (!_helper.ValidateDate(inputEndDate, out DateTime endDate))
{
AnsiConsole.MarkupLine("[red]Invalid start date format![/]");
continue;
}

if (_helper.IsSessionDatesValid(startDate, endDate))
{
_trackerService.InsertSession(startDate, endDate);
confirm = true;
AnsiConsole.MarkupLine("[green]Session successfully added![/]");
}
}
}

public void ViewAllSessions()
{
List<Tracker> allSessions = _trackerService.GetAllSession();

if (allSessions.Any())
{
_helper.CreateTable(allSessions);
}
else
{
AnsiConsole.MarkupLine("I could not find any session.");
}

}
public void DeleteSession()
{
List<Tracker> allSession = _trackerService.GetAllSession();

if (allSession.Count == 0)
{
AnsiConsole.MarkupLine("[red]No session is available to delete.[/]");
Console.ReadKey();
}

var sessionToDelete = AnsiConsole.Prompt(new SelectionPrompt<Tracker>()
.Title("Select a [red]session[/] to delete:").UseConverter(s => $"Start: {s.StartTime} | End: {s.EndTime} - {s.Duration} elapsed.").AddChoices(allSession));

if (_helper.ConfirmMessage("Delete", sessionToDelete.StartTime.ToString()))
{
_trackerService.DeleteSession(sessionToDelete);
}
else
{
AnsiConsole.MarkupLine("Deletion Canceled.");
}

AnsiConsole.MarkupLine("[green]Press Any Key to Continue.[/]");
Console.ReadKey();
}
public void EditSession()
{
List<Tracker> allSession = _trackerService.GetAllSession();

if (allSession.Count == 0)
{
AnsiConsole.MarkupLine("[red]No session is available to delete.[/]");
Console.ReadKey();
}

var sessionToEdit = AnsiConsole.Prompt(new SelectionPrompt<Tracker>()
.Title("Select a [cyan]session[/] to edit:").UseConverter(s => $"Start: {s.StartTime} | End: {s.EndTime} - {s.Duration} elapsed.").AddChoices(allSession));

Console.Clear();

bool confirm = false;
while (!confirm)
{
var inputStartDate = AnsiConsole.Prompt(
new TextPrompt<string>($"Enter the [green]new start time[/] (dd/MM/yyyy HH:mm:ss)\n[grey](default: {sessionToEdit.StartTime:dd/MM/yyyy HH:mm:ss})[/]"));
if (!_helper.ValidateDate(inputStartDate, out DateTime newStartingTime))
{
AnsiConsole.MarkupLine("[red]Invalid start date format![/]");
continue;
}

var inputEndDate = AnsiConsole.Prompt(
new TextPrompt<string>($"Enter the [green]new end time[/] (dd/MM/yyyy HH:mm:ss)\n[grey](default: {sessionToEdit.EndTime:dd/MM/yyyy HH:mm:ss})[/]"));
if (!_helper.ValidateDate(inputEndDate, out DateTime newEndingTime))
{
AnsiConsole.MarkupLine("[red]Invalid end date format![/]");
continue;
}

if (_helper.IsSessionDatesValid(newStartingTime, newEndingTime))
{
sessionToEdit.StartTime = newStartingTime;
sessionToEdit.EndTime = newEndingTime;
confirm = true;
}
}

_trackerService.UpdateSession(sessionToEdit);
}

public void ViewSessionsByDate(bool ascending)
{
List<Tracker> allSessions = _trackerService.GetSessionsOrderByDate(ascending);

if (allSessions.Any())
{
_helper.CreateTable(allSessions);
}
else
{
AnsiConsole.MarkupLine("I could not find any session.");
}

}
}
Loading