From 69a96953e46ce8c50e399009a5a6354650716f78 Mon Sep 17 00:00:00 2001 From: kilozdazolik Date: Fri, 6 Mar 2026 18:32:19 +0100 Subject: [PATCH 1/3] Initial commit: DocumentProcessor with tests --- .gitignore | 397 ++++++++++++++++++ Data/PhonebookContext.cs | 33 ++ .../20260224194145_InitialCreate.Designer.cs | 139 ++++++ Migrations/20260224194145_InitialCreate.cs | 88 ++++ Migrations/PhoneBookContextModelSnapshot.cs | 136 ++++++ Models/Category.cs | 11 + Models/Contact.cs | 15 + Models/Dtos.cs | 16 + Models/ReportScheduler.cs | 9 + Program.cs | 30 ++ Services/ExportService.cs | 37 ++ Services/ImportService.cs | 87 ++++ Services/Interfaces/IExportService.cs | 11 + Services/Interfaces/IImportService.cs | 12 + Services/Interfaces/IReportService.cs | 7 + Services/ReportService.cs | 104 +++++ Services/WorkerService.cs | 48 +++ UI.cs | 152 +++++++ appsettings.json | 5 + kilozdazolik.DocumentProcessor.csproj | 35 ++ kilozdazolik.DocumentProcessor.slnx | 4 + phonebook.db | Bin 0 -> 32768 bytes 22 files changed, 1376 insertions(+) create mode 100644 .gitignore create mode 100644 Data/PhonebookContext.cs create mode 100644 Migrations/20260224194145_InitialCreate.Designer.cs create mode 100644 Migrations/20260224194145_InitialCreate.cs create mode 100644 Migrations/PhoneBookContextModelSnapshot.cs create mode 100644 Models/Category.cs create mode 100644 Models/Contact.cs create mode 100644 Models/Dtos.cs create mode 100644 Models/ReportScheduler.cs create mode 100644 Program.cs create mode 100644 Services/ExportService.cs create mode 100644 Services/ImportService.cs create mode 100644 Services/Interfaces/IExportService.cs create mode 100644 Services/Interfaces/IImportService.cs create mode 100644 Services/Interfaces/IReportService.cs create mode 100644 Services/ReportService.cs create mode 100644 Services/WorkerService.cs create mode 100644 UI.cs create mode 100644 appsettings.json create mode 100644 kilozdazolik.DocumentProcessor.csproj create mode 100644 kilozdazolik.DocumentProcessor.slnx create mode 100644 phonebook.db diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8b1963 --- /dev/null +++ b/.gitignore @@ -0,0 +1,397 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea/ diff --git a/Data/PhonebookContext.cs b/Data/PhonebookContext.cs new file mode 100644 index 0000000..8a5883e --- /dev/null +++ b/Data/PhonebookContext.cs @@ -0,0 +1,33 @@ +using kilozdazolik.DocumentProcessor.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; + + +namespace kilozdazolik.DocumentProcessor.Data; + +public class PhoneBookContext : DbContext +{ + public PhoneBookContext() { } + public PhoneBookContext(DbContextOptions options) : base(options) { } + + public DbSet Categories { get; set; } + public DbSet Contacts { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + + if (optionsBuilder.IsConfigured) + { + return; + } + + var config = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false) + .Build(); + + var connectionString = config.GetConnectionString("DefaultConnection"); + + optionsBuilder.UseSqlite(connectionString); + } +} \ No newline at end of file diff --git a/Migrations/20260224194145_InitialCreate.Designer.cs b/Migrations/20260224194145_InitialCreate.Designer.cs new file mode 100644 index 0000000..58fecd3 --- /dev/null +++ b/Migrations/20260224194145_InitialCreate.Designer.cs @@ -0,0 +1,139 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using kilozdazolik.DocumentProcessor.Data; + +#nullable disable + +namespace kilozdazolik.DocumentProcessor.Migrations +{ + [DbContext(typeof(PhoneBookContext))] + [Migration("20260224194145_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.3"); + + modelBuilder.Entity("kilozdazolik.DocumentProcessor.Models.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("CategoryId"); + + b.ToTable("Categories"); + + b.HasData( + new + { + CategoryId = 1, + Name = "Default" + }, + new + { + CategoryId = 2, + Name = "Friends" + }, + new + { + CategoryId = 3, + Name = "Family" + }, + new + { + CategoryId = 4, + Name = "Work" + }); + }); + + modelBuilder.Entity("kilozdazolik.DocumentProcessor.Models.Contact", b => + { + b.Property("ContactId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CategoryId") + .HasColumnType("INTEGER"); + + b.Property("Email") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("ContactId"); + + b.HasIndex("CategoryId"); + + b.ToTable("Contacts"); + + b.HasData( + new + { + ContactId = 1, + CategoryId = 1, + Email = "john.doe@example.com", + Name = "John Doe", + PhoneNumber = "21545678396" + }, + new + { + ContactId = 2, + CategoryId = 2, + Email = "john.smith@example.com", + Name = "John Smith", + PhoneNumber = "21545678996" + }, + new + { + ContactId = 3, + CategoryId = 1, + Email = "john.jones@example.com", + Name = "John Jones", + PhoneNumber = "21545678912" + }, + new + { + ContactId = 4, + CategoryId = 3, + Email = "adam.forest@example.com", + Name = "Adam Forest", + PhoneNumber = "21515271912" + }); + }); + + modelBuilder.Entity("kilozdazolik.DocumentProcessor.Models.Contact", b => + { + b.HasOne("kilozdazolik.DocumentProcessor.Models.Category", "Category") + .WithMany("Contacts") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("kilozdazolik.DocumentProcessor.Models.Category", b => + { + b.Navigation("Contacts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20260224194145_InitialCreate.cs b/Migrations/20260224194145_InitialCreate.cs new file mode 100644 index 0000000..369d575 --- /dev/null +++ b/Migrations/20260224194145_InitialCreate.cs @@ -0,0 +1,88 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace kilozdazolik.DocumentProcessor.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Categories", + columns: table => new + { + CategoryId = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Categories", x => x.CategoryId); + }); + + migrationBuilder.CreateTable( + name: "Contacts", + columns: table => new + { + ContactId = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", nullable: false), + Email = table.Column(type: "TEXT", nullable: false), + PhoneNumber = table.Column(type: "TEXT", nullable: false), + CategoryId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Contacts", x => x.ContactId); + table.ForeignKey( + name: "FK_Contacts_Categories_CategoryId", + column: x => x.CategoryId, + principalTable: "Categories", + principalColumn: "CategoryId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.InsertData( + table: "Categories", + columns: new[] { "CategoryId", "Name" }, + values: new object[,] + { + { 1, "Default" }, + { 2, "Friends" }, + { 3, "Family" }, + { 4, "Work" } + }); + + migrationBuilder.InsertData( + table: "Contacts", + columns: new[] { "ContactId", "CategoryId", "Email", "Name", "PhoneNumber" }, + values: new object[,] + { + { 1, 1, "john.doe@example.com", "John Doe", "21545678396" }, + { 2, 2, "john.smith@example.com", "John Smith", "21545678996" }, + { 3, 1, "john.jones@example.com", "John Jones", "21545678912" }, + { 4, 3, "adam.forest@example.com", "Adam Forest", "21515271912" } + }); + + migrationBuilder.CreateIndex( + name: "IX_Contacts_CategoryId", + table: "Contacts", + column: "CategoryId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Contacts"); + + migrationBuilder.DropTable( + name: "Categories"); + } + } +} diff --git a/Migrations/PhoneBookContextModelSnapshot.cs b/Migrations/PhoneBookContextModelSnapshot.cs new file mode 100644 index 0000000..2028217 --- /dev/null +++ b/Migrations/PhoneBookContextModelSnapshot.cs @@ -0,0 +1,136 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using kilozdazolik.DocumentProcessor.Data; + +#nullable disable + +namespace kilozdazolik.DocumentProcessor.Migrations +{ + [DbContext(typeof(PhoneBookContext))] + partial class PhoneBookContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.3"); + + modelBuilder.Entity("kilozdazolik.DocumentProcessor.Models.Category", b => + { + b.Property("CategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("CategoryId"); + + b.ToTable("Categories"); + + b.HasData( + new + { + CategoryId = 1, + Name = "Default" + }, + new + { + CategoryId = 2, + Name = "Friends" + }, + new + { + CategoryId = 3, + Name = "Family" + }, + new + { + CategoryId = 4, + Name = "Work" + }); + }); + + modelBuilder.Entity("kilozdazolik.DocumentProcessor.Models.Contact", b => + { + b.Property("ContactId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CategoryId") + .HasColumnType("INTEGER"); + + b.Property("Email") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("ContactId"); + + b.HasIndex("CategoryId"); + + b.ToTable("Contacts"); + + b.HasData( + new + { + ContactId = 1, + CategoryId = 1, + Email = "john.doe@example.com", + Name = "John Doe", + PhoneNumber = "21545678396" + }, + new + { + ContactId = 2, + CategoryId = 2, + Email = "john.smith@example.com", + Name = "John Smith", + PhoneNumber = "21545678996" + }, + new + { + ContactId = 3, + CategoryId = 1, + Email = "john.jones@example.com", + Name = "John Jones", + PhoneNumber = "21545678912" + }, + new + { + ContactId = 4, + CategoryId = 3, + Email = "adam.forest@example.com", + Name = "Adam Forest", + PhoneNumber = "21515271912" + }); + }); + + modelBuilder.Entity("kilozdazolik.DocumentProcessor.Models.Contact", b => + { + b.HasOne("kilozdazolik.DocumentProcessor.Models.Category", "Category") + .WithMany("Contacts") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("kilozdazolik.DocumentProcessor.Models.Category", b => + { + b.Navigation("Contacts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Models/Category.cs b/Models/Category.cs new file mode 100644 index 0000000..f0492e9 --- /dev/null +++ b/Models/Category.cs @@ -0,0 +1,11 @@ + + +namespace kilozdazolik.DocumentProcessor.Models +{ + public class Category + { + public int CategoryId { get; set; } + public string Name { get; set; } + public List Contacts { get; set; } + } +} diff --git a/Models/Contact.cs b/Models/Contact.cs new file mode 100644 index 0000000..1edf2d8 --- /dev/null +++ b/Models/Contact.cs @@ -0,0 +1,15 @@ +namespace kilozdazolik.DocumentProcessor.Models +{ + public class Contact + { + public int ContactId { get; set; } + public string Name { get; set; } = null!; + public string? Email { get; set; } + public string PhoneNumber { get; set; } = null!; + + public int CategoryId { get; set; } + + public Category Category { get; set; } + + } +} diff --git a/Models/Dtos.cs b/Models/Dtos.cs new file mode 100644 index 0000000..7c48814 --- /dev/null +++ b/Models/Dtos.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace kilozdazolik.DocumentProcessor.Models +{ + public class Dtos + { + public record ContactDto( + string Name, + string Email, + string PhoneNumber, + string CategoryName + ); + } +} diff --git a/Models/ReportScheduler.cs b/Models/ReportScheduler.cs new file mode 100644 index 0000000..3390e99 --- /dev/null +++ b/Models/ReportScheduler.cs @@ -0,0 +1,9 @@ +namespace kilozdazolik.DocumentProcessor.Models +{ + public class ReportScheduler + { + public bool IsEnabled { get; set; } = false; + public int IntervalMinutes { get; set; } = 10; + public string OutputPath { get; set; } = "AutomaticReport.pdf"; + } +} diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..799bb27 --- /dev/null +++ b/Program.cs @@ -0,0 +1,30 @@ +using kilozdazolik.DocumentProcessor; +using kilozdazolik.DocumentProcessor.Data; +using kilozdazolik.DocumentProcessor.Models; +using kilozdazolik.DocumentProcessor.Services; +using kilozdazolik.DocumentProcessor.Services.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +var builder = Host.CreateApplicationBuilder(args); + +builder.Services.AddDbContext(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddHostedService(); + +using IHost host = builder.Build(); + +await host.StartAsync(); + +using (var scope = host.Services.CreateScope()) +{ + var ui = scope.ServiceProvider.GetRequiredService(); + await ui.RunAsync(); +} + +await host.StopAsync(); \ No newline at end of file diff --git a/Services/ExportService.cs b/Services/ExportService.cs new file mode 100644 index 0000000..43b0b9f --- /dev/null +++ b/Services/ExportService.cs @@ -0,0 +1,37 @@ +using CsvHelper; +using CsvHelper.Configuration; +using kilozdazolik.DocumentProcessor.Data; +using kilozdazolik.DocumentProcessor.Models; +using kilozdazolik.DocumentProcessor.Services.Interfaces; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System.Globalization; + +namespace kilozdazolik.DocumentProcessor.Services +{ + public class ExportService(PhoneBookContext context, ILogger logger) : IExportService + { + public async Task ExportToCsvAsync(string filePath) + { + var contacts = await FetchContactsAsync(); + await WriteCsvAsync(filePath, contacts); + logger.LogInformation("Export completed. {Count} records exported.", contacts.Count); + } + + private async Task> FetchContactsAsync() + { + return await context.Contacts + .Include(c => c.Category) + .Select(c => new Dtos.ContactDto(c.Name, c.Email, c.PhoneNumber, c.Category.Name)) + .ToListAsync(); + } + + private static async Task WriteCsvAsync(string filePath, List contacts) + { + var config = new CsvConfiguration(CultureInfo.InvariantCulture) { Delimiter = ";" }; + await using var writer = new StreamWriter(filePath); + await using var csv = new CsvWriter(writer, config); + await csv.WriteRecordsAsync(contacts); + } + } +} \ No newline at end of file diff --git a/Services/ImportService.cs b/Services/ImportService.cs new file mode 100644 index 0000000..d8c8657 --- /dev/null +++ b/Services/ImportService.cs @@ -0,0 +1,87 @@ +using CsvHelper; +using CsvHelper.Configuration; +using kilozdazolik.DocumentProcessor.Data; +using kilozdazolik.DocumentProcessor.Models; +using kilozdazolik.DocumentProcessor.Services.Interfaces; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System.Globalization; + +namespace kilozdazolik.DocumentProcessor.Services +{ + public record ImportResult(int ImportedCount, List Errors); + + public class ImportService(PhoneBookContext context, ILogger logger) : IImportService + { + public async Task ImportFromCsvAsync(string filePath) + { + using var reader = new StreamReader(filePath); + var config = new CsvConfiguration(CultureInfo.InvariantCulture) { Delimiter = ";" }; + using var csv = new CsvReader(reader, config); + var records = csv.GetRecords(); + return await ValidateAndImportAsync(records); + } + + public async Task ValidateAndImportAsync(IEnumerable dtos) + { + var categories = await context.Categories.ToDictionaryAsync(c => c.Name.ToLower(), c => c); + var existingEmails = new HashSet( + await context.Contacts.Select(c => c.Email).ToListAsync(), + StringComparer.OrdinalIgnoreCase + ); + + var errors = new List(); + int importedCount = 0; + + foreach (var dto in dtos) + { + var error = Validate(dto, categories, existingEmails); + if (error is not null) + { + logger.LogWarning(error); + errors.Add(error); + continue; + } + + var contact = MapToContact(dto, categories[dto.CategoryName.ToLower()]); + + if (!string.IsNullOrWhiteSpace(dto.Email)) + { + existingEmails.Add(dto.Email); + } + + context.Contacts.Add(contact); + importedCount++; + } + + await context.SaveChangesAsync(); + logger.LogInformation("Import completed. {ImportedCount} records imported.", importedCount); + return new ImportResult(importedCount, errors); + } + + private static string? Validate(Dtos.ContactDto dto, Dictionary categories, HashSet existingEmails) + { + if (string.IsNullOrWhiteSpace(dto.Name) || string.IsNullOrWhiteSpace(dto.PhoneNumber)) + return $"SKIPPING: Record with empty Name or PhoneNumber."; + + if (string.IsNullOrWhiteSpace(dto.CategoryName)) + return $"SKIPPING: Record with empty CategoryName."; + + if (!string.IsNullOrEmpty(dto.Email) && existingEmails.Contains(dto.Email)) + return $"SKIPPING: '{dto.Email}' already exists."; + + if (!categories.ContainsKey(dto.CategoryName.ToLower())) + return $"SKIPPING: Category '{dto.CategoryName}' not found."; + + return null; + } + + private static Contact MapToContact(Dtos.ContactDto dto, Category category) => new() + { + Name = dto.Name, + Email = dto.Email, + PhoneNumber = dto.PhoneNumber, + CategoryId = category.CategoryId + }; + } +} \ No newline at end of file diff --git a/Services/Interfaces/IExportService.cs b/Services/Interfaces/IExportService.cs new file mode 100644 index 0000000..b4deb3b --- /dev/null +++ b/Services/Interfaces/IExportService.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace kilozdazolik.DocumentProcessor.Services.Interfaces +{ + public interface IExportService + { + Task ExportToCsvAsync(string path); + } +} diff --git a/Services/Interfaces/IImportService.cs b/Services/Interfaces/IImportService.cs new file mode 100644 index 0000000..d374139 --- /dev/null +++ b/Services/Interfaces/IImportService.cs @@ -0,0 +1,12 @@ +using kilozdazolik.DocumentProcessor.Models; +using System; +using System.Collections.Generic; +using System.Text; + +namespace kilozdazolik.DocumentProcessor.Services.Interfaces +{ + public interface IImportService + { + Task ImportFromCsvAsync(string path); + } +} diff --git a/Services/Interfaces/IReportService.cs b/Services/Interfaces/IReportService.cs new file mode 100644 index 0000000..8c1b42b --- /dev/null +++ b/Services/Interfaces/IReportService.cs @@ -0,0 +1,7 @@ +namespace kilozdazolik.DocumentProcessor.Services.Interfaces +{ + public interface IReportService + { + Task ExportToPdfAsync(string filePath); + } +} \ No newline at end of file diff --git a/Services/ReportService.cs b/Services/ReportService.cs new file mode 100644 index 0000000..99c829b --- /dev/null +++ b/Services/ReportService.cs @@ -0,0 +1,104 @@ +using kilozdazolik.DocumentProcessor.Data; +using kilozdazolik.DocumentProcessor.Services.Interfaces; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace kilozdazolik.DocumentProcessor.Services +{ + public class ReportService(PhoneBookContext context, ILogger logger) : IReportService + { + public async Task ExportToPdfAsync(string filePath) + { + var contacts = await context.Contacts + .Include(c => c.Category) + .OrderBy(c => c.Category.Name) + .ThenBy(c => c.Name) + .ToListAsync(); + + QuestPDF.Settings.License = LicenseType.Community; + + Document.Create(container => + { + container.Page(page => + { + page.Size(PageSizes.A4); + page.Margin(2, Unit.Centimetre); + page.DefaultTextStyle(x => x.FontSize(11)); + + page.Header().Element(ComposeHeader); + page.Content().Element(content => ComposeContent(content, contacts)); + page.Footer().AlignCenter().Text(x => + { + x.Span("Page "); + x.CurrentPageNumber(); + x.Span(" of "); + x.TotalPages(); + }); + }); + }) + .GeneratePdf(filePath); + + logger.LogInformation("PDF report generated at {FilePath} with {Count} contacts.", filePath, contacts.Count); + } + + private static void ComposeHeader(IContainer container) + { + container.Row(row => + { + row.RelativeItem().Column(col => + { + col.Item().Text("PhoneBook Report") + .FontSize(20).Bold().FontColor(Colors.Teal.Medium); + col.Item().Text($"Generated: {DateTime.Now:dd MMM yyyy HH:mm}") + .FontSize(9).FontColor(Colors.Grey.Medium); + }); + }); + } + + private static void ComposeContent(IContainer container, List contacts) + { + container.PaddingTop(10).Column(col => + { + col.Item().Text($"Total contacts: {contacts.Count}") + .FontSize(10).FontColor(Colors.Grey.Darken2).Italic(); + + col.Item().PaddingTop(10).Table(table => + { + table.ColumnsDefinition(cols => + { + cols.RelativeColumn(3); // Name + cols.RelativeColumn(4); // Email + cols.RelativeColumn(2); // Phone + cols.RelativeColumn(2); // Category + }); + + // Header + table.Header(header => + { + foreach (var title in new[] { "Name", "Email", "Phone", "Category" }) + { + header.Cell().Background(Colors.Teal.Medium).Padding(5) + .Text(title).FontColor(Colors.White).Bold(); + } + }); + + // Rows + var isAlternate = false; + foreach (var contact in contacts) + { + var bg = isAlternate ? Colors.Grey.Lighten3 : Colors.White; + isAlternate = !isAlternate; + + foreach (var value in new[] { contact.Name, contact.Email ?? "-", contact.PhoneNumber, contact.Category?.Name ?? "-" }) + { + table.Cell().Background(bg).Padding(5).Text(value); + } + } + }); + }); + } + } +} \ No newline at end of file diff --git a/Services/WorkerService.cs b/Services/WorkerService.cs new file mode 100644 index 0000000..6145dc5 --- /dev/null +++ b/Services/WorkerService.cs @@ -0,0 +1,48 @@ +using kilozdazolik.DocumentProcessor.Models; +using kilozdazolik.DocumentProcessor.Services.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace kilozdazolik.DocumentProcessor.Services +{ + public class WorkerService( + IServiceScopeFactory scopeFactory, + ILogger logger, + ReportScheduler scheduler) : BackgroundService + { + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + logger.LogInformation("Background Report Worker started."); + + while (!stoppingToken.IsCancellationRequested) + { + if (scheduler.IsEnabled) + { + try + { + using (var scope = scopeFactory.CreateScope()) + { + var reportService = scope.ServiceProvider.GetRequiredService(); + + var finalPath = scheduler.OutputPath.Replace(".pdf", $"_{DateTime.Now:yyyyMMdd_HHmm}.pdf"); + + await reportService.ExportToPdfAsync(finalPath); + logger.LogInformation("Background report generated: {Path}", finalPath); + } + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to generate background report."); + } + + await Task.Delay(TimeSpan.FromMinutes(scheduler.IntervalMinutes), stoppingToken); + } + else + { + await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); + } + } + } + } +} \ No newline at end of file diff --git a/UI.cs b/UI.cs new file mode 100644 index 0000000..8a9560e --- /dev/null +++ b/UI.cs @@ -0,0 +1,152 @@ +using kilozdazolik.DocumentProcessor.Models; +using kilozdazolik.DocumentProcessor.Services; +using kilozdazolik.DocumentProcessor.Services.Interfaces; +using Spectre.Console; + +namespace kilozdazolik.DocumentProcessor +{ + public class UI(IImportService importService, + IExportService exportService, + IReportService reportService, + ReportScheduler scheduler) + { + public async Task RunAsync() + { + AnsiConsole.Write( + new FigletText("PhoneBook") + .Centered()); + + while (true) + { + var choice = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("[grey]What would you like to do?[/]") + .AddChoices("Import from CSV", "Export to CSV", "Generate PDF Report", "Exit")); + + switch (choice) + { + case "Import from CSV": + await HandleImportAsync(); + break; + case "Export to CSV": + await HandleExportAsync(); + break; + case "Generate PDF Report": + await HandleReportAsync(); + break; + case "Exit": + AnsiConsole.MarkupLine("[grey]Goodbye.[/]"); + return; + } + } + } + + private async Task HandleImportAsync() + { + var filePath = AnsiConsole.Ask("Enter [aqua]CSV file path[/] to import:"); + + if (!File.Exists(filePath)) + { + AnsiConsole.MarkupLine($"[red]File not found:[/] {filePath}"); + return; + } + + try + { + ImportResult? result = null; + await AnsiConsole.Status() + .Spinner(Spinner.Known.Dots) + .StartAsync("Importing...", async _ => + { + result = await importService.ImportFromCsvAsync(filePath); + }); + + AnsiConsole.MarkupLine($"[green]Done![/] Imported [aqua]{result!.ImportedCount}[/] records."); + + if (result.Errors.Count > 0) + { + var table = new Table() + .Border(TableBorder.Rounded) + .BorderColor(Spectre.Console.Color.Red) + .AddColumn("[red]Skipped / Errors[/]"); + + foreach (var error in result.Errors) + table.AddRow(Markup.Escape(error)); + + AnsiConsole.Write(table); + } + } + catch (Exception ex) + { + AnsiConsole.MarkupLine($"[red]Import failed:[/] {Markup.Escape(ex.Message)}"); + } + } + + private async Task HandleExportAsync() + { + var filePath = AnsiConsole.Ask("Enter [aqua]output file path[/]:", "contacts.csv"); + + try + { + await AnsiConsole.Status() + .Spinner(Spinner.Known.Dots) + .StartAsync("Exporting...", async _ => + { + await exportService.ExportToCsvAsync(filePath); + }); + + AnsiConsole.MarkupLine($"[green]Done![/] Exported to [aqua]{filePath}[/]."); + } + catch (Exception ex) + { + AnsiConsole.MarkupLine($"[red]Export failed:[/] {Markup.Escape(ex.Message)}"); + } + } + + private async Task HandleReportAsync() + { + var mode = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Report Mode") + .AddChoices("Generate Once", "Setup Auto-Generator", "Disable Auto-Generator")); + + if (mode == "Generate Once") + { + var filePath = AnsiConsole.Ask("Enter [aqua]output PDF[/]:", "phonebook_report.pdf"); + + try + { + await AnsiConsole.Status() + .Spinner(Spinner.Known.Dots) + .StartAsync("Generating report...", async _ => + { + await reportService.ExportToPdfAsync(filePath); + }); + + AnsiConsole.MarkupLine($"[green]Done![/] Report saved to [aqua]{filePath}[/]."); + } + catch (Exception ex) + { + AnsiConsole.MarkupLine($"[red]Report failed:[/] {Markup.Escape(ex.Message)}"); + } + } + else if (mode == "Setup Auto-Generator") + { + var minutes = AnsiConsole.Ask("Enter interval in [aqua]minutes[/]:", 10); + var path = AnsiConsole.Ask("Enter [aqua]filename[/] for auto-reports:", "auto_report.pdf"); + + // 3. Use 'scheduler' (the name from your constructor) + scheduler.IntervalMinutes = minutes; + scheduler.OutputPath = path; + scheduler.IsEnabled = true; + + AnsiConsole.MarkupLine($"[green]Auto-generator enabled![/] Running every {minutes} minutes."); + } + else + { + scheduler.IsEnabled = false; + AnsiConsole.MarkupLine("[yellow]Auto-generator disabled.[/]"); + } + } + } +} \ No newline at end of file diff --git a/appsettings.json b/appsettings.json new file mode 100644 index 0000000..8d1fac7 --- /dev/null +++ b/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Data Source=phonebook.db" + } +} \ No newline at end of file diff --git a/kilozdazolik.DocumentProcessor.csproj b/kilozdazolik.DocumentProcessor.csproj new file mode 100644 index 0000000..64b3f3e --- /dev/null +++ b/kilozdazolik.DocumentProcessor.csproj @@ -0,0 +1,35 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/kilozdazolik.DocumentProcessor.slnx b/kilozdazolik.DocumentProcessor.slnx new file mode 100644 index 0000000..4dcbb83 --- /dev/null +++ b/kilozdazolik.DocumentProcessor.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/phonebook.db b/phonebook.db new file mode 100644 index 0000000000000000000000000000000000000000..5a99688646c72b8616adc02500f8e639b71a088b GIT binary patch literal 32768 zcmeI*?Qhyf90%|_n?OlN+)0~IRYkivo75-`vmFQ}+lw+dI$B~Vpd*b12d{3X>8R0Zs(bB{*Y~ZCKWvvQ-+tl^ zURJx&t}CS`FSaZBrd?W=dIcK zv-^CX6(6?hRZ}#x!_C&MQLU!s+A3@DJyFxBw&*49T2AkR*BS?2-!_ljNA}=?BhC!{ zTz^L`ob7dG((8!dvCl?AWtDo9SMQqPSf*)NlUI0?nUxQJt^ zxLYcgd1`U3u@FzMuhac7D8uJHu_kH{?dL~!zhlpA#-~Cw^Wx3Ekh~>It{oe(M0#a~ zo_u&3=y880JzJQnX}Zf%xW{wwHR3&P;=e^)FdXkmiF7_sPwHW(+ihOiah?n;-|_mx zPo1GJPKUWW<>{f!Jw^m#G^%l(!8D$lb+dE$f{M;e=xClc24455<9}ukhN81G)rz52 z<#_swJSE|2K8l)kUweJYc>p&45nnz3PInO@k=>f5>787;4CoBGzerfq9lu>PmY4@BIMAOHafKmY;| zfB*y_009U<00I!WYyuxpnO_f<|C(WB^+Hz9=GxW1<2zQbG_b|Lis)KK%WN)+aZ%-G zB5p_!fB*y_009U<00Izz00bZa0SH`Df%jydeAQn8g3teB%J)S1OZip#No0^9009U< z00Izz00bZa0SG_<0uZJgJWa|nxn8kcr}uJElF5zAz_I(?;iC9vz>Tu~ z*gER@!TMiPej&;q;sFT)5P$##AOHafKmY;|fB*y_0D(&?@RlSm)9Cu5u_Y?6(8(nz zW2c}0XNmHi@~v_xF4T_%0SG_<0uX=z1Rwwb2tWV=5P(1^kd4L3>PM@z*tJ|%@dm-Q z^+h%#end}X@7OQImG*mfrsKJWp3`$iUN7i|tjlq-Qdmvi^A7s#p7`O6;i1T74ujlG znQU$=e_NC>q_7ALi|o3Ne-IWK2D!OK3R}_!jg#fvsF|{7hb6k6J@5FR{j(jBnUtN0uX=z1Rwwb2tWV=5P$##F0;UbOsO0T9wjLzC#fvU$>hJNtO(lx literal 0 HcmV?d00001 From 74a152f465ecc776918babc04b15f061d6a79804 Mon Sep 17 00:00:00 2001 From: kilozdazolik Date: Fri, 6 Mar 2026 18:40:17 +0100 Subject: [PATCH 2/3] Reorganize project folders and add proper ignore rules --- .gitignore | 409 +----------------- kilozdazolik.DocumentProcessor.slnx | 4 - kilozdazolik.DocumentProcessor/.gitignore | 397 +++++++++++++++++ .../Data}/PhonebookContext.cs | 0 .../20260224194145_InitialCreate.Designer.cs | 0 .../20260224194145_InitialCreate.cs | 0 .../PhoneBookContextModelSnapshot.cs | 0 .../Models}/Category.cs | 0 .../Models}/Contact.cs | 0 .../Models}/Dtos.cs | 0 .../Models}/ReportScheduler.cs | 0 .../Program.cs | 0 .../Services}/ExportService.cs | 0 .../Services}/ImportService.cs | 0 .../Services}/Interfaces/IExportService.cs | 0 .../Services}/Interfaces/IImportService.cs | 0 .../Services}/Interfaces/IReportService.cs | 0 .../Services}/ReportService.cs | 0 .../Services}/WorkerService.cs | 0 UI.cs => kilozdazolik.DocumentProcessor/UI.cs | 0 .../appsettings.json | 0 .../kilozdazolik.DocumentProcessor.csproj | 0 .../kilozdazolik.DocumentProcessor.slnx | 4 + .../ImportServiceTests.cs | 78 ++++ .../ReportSchedulerTests.cs | 21 + ...kilozdazolik.DocumentProcessorTests.csproj | 33 ++ phonebook.db | Bin 32768 -> 0 bytes 27 files changed, 556 insertions(+), 390 deletions(-) delete mode 100644 kilozdazolik.DocumentProcessor.slnx create mode 100644 kilozdazolik.DocumentProcessor/.gitignore rename {Data => kilozdazolik.DocumentProcessor/Data}/PhonebookContext.cs (100%) rename {Migrations => kilozdazolik.DocumentProcessor/Migrations}/20260224194145_InitialCreate.Designer.cs (100%) rename {Migrations => kilozdazolik.DocumentProcessor/Migrations}/20260224194145_InitialCreate.cs (100%) rename {Migrations => kilozdazolik.DocumentProcessor/Migrations}/PhoneBookContextModelSnapshot.cs (100%) rename {Models => kilozdazolik.DocumentProcessor/Models}/Category.cs (100%) rename {Models => kilozdazolik.DocumentProcessor/Models}/Contact.cs (100%) rename {Models => kilozdazolik.DocumentProcessor/Models}/Dtos.cs (100%) rename {Models => kilozdazolik.DocumentProcessor/Models}/ReportScheduler.cs (100%) rename Program.cs => kilozdazolik.DocumentProcessor/Program.cs (100%) rename {Services => kilozdazolik.DocumentProcessor/Services}/ExportService.cs (100%) rename {Services => kilozdazolik.DocumentProcessor/Services}/ImportService.cs (100%) rename {Services => kilozdazolik.DocumentProcessor/Services}/Interfaces/IExportService.cs (100%) rename {Services => kilozdazolik.DocumentProcessor/Services}/Interfaces/IImportService.cs (100%) rename {Services => kilozdazolik.DocumentProcessor/Services}/Interfaces/IReportService.cs (100%) rename {Services => kilozdazolik.DocumentProcessor/Services}/ReportService.cs (100%) rename {Services => kilozdazolik.DocumentProcessor/Services}/WorkerService.cs (100%) rename UI.cs => kilozdazolik.DocumentProcessor/UI.cs (100%) rename appsettings.json => kilozdazolik.DocumentProcessor/appsettings.json (100%) rename kilozdazolik.DocumentProcessor.csproj => kilozdazolik.DocumentProcessor/kilozdazolik.DocumentProcessor.csproj (100%) create mode 100644 kilozdazolik.DocumentProcessor/kilozdazolik.DocumentProcessor.slnx create mode 100644 kilozdazolik.DocumentProcessorTests/ImportServiceTests.cs create mode 100644 kilozdazolik.DocumentProcessorTests/ReportSchedulerTests.cs create mode 100644 kilozdazolik.DocumentProcessorTests/kilozdazolik.DocumentProcessorTests.csproj delete mode 100644 phonebook.db diff --git a/.gitignore b/.gitignore index b8b1963..4d6444c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,397 +1,34 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. +# Build output +**/bin/ +**/obj/ -# User-specific files -*.rsuser -*.suo +# IDE / user-specific +.vs/ +.vscode/ *.user +*.suo +*.rsuser *.userosscache *.sln.docstates -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Ww][Ii][Nn]32/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ -[Ll]ogs/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET -project.lock.json -project.fragment.lock.json -artifacts/ - -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.tlog -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Coverlet is a free, cross platform Code Coverage Tool +# Test and coverage output +TestResults/ +coverage/ coverage*.json coverage*.xml -coverage*.info - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio 6 auto-generated project file (contains which files were open etc.) -*.vbp - -# Visual Studio 6 workspace and project file (working project files containing files to include in project) -*.dsw -*.dsp - -# Visual Studio 6 technical files -*.ncb -*.aps - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# Visual Studio History (VSHistory) files -.vshistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - -# Fody - auto-generated XML schema -FodyWeavers.xsd - -# VS Code files for those working on multiple tools -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace +# Logs and temp files +*.log +*.tmp -# Local History for Visual Studio Code -.history/ +# NuGet packages folder (if created locally) +**/packages/ -# Windows Installer files from build outputs -*.cab -*.msi -*.msix -*.msm -*.msp +# Local database files +*.db +*.db-shm +*.db-wal -# JetBrains Rider -*.sln.iml -.idea/ +# OS files +.DS_Store +Thumbs.db diff --git a/kilozdazolik.DocumentProcessor.slnx b/kilozdazolik.DocumentProcessor.slnx deleted file mode 100644 index 4dcbb83..0000000 --- a/kilozdazolik.DocumentProcessor.slnx +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/kilozdazolik.DocumentProcessor/.gitignore b/kilozdazolik.DocumentProcessor/.gitignore new file mode 100644 index 0000000..b8b1963 --- /dev/null +++ b/kilozdazolik.DocumentProcessor/.gitignore @@ -0,0 +1,397 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea/ diff --git a/Data/PhonebookContext.cs b/kilozdazolik.DocumentProcessor/Data/PhonebookContext.cs similarity index 100% rename from Data/PhonebookContext.cs rename to kilozdazolik.DocumentProcessor/Data/PhonebookContext.cs diff --git a/Migrations/20260224194145_InitialCreate.Designer.cs b/kilozdazolik.DocumentProcessor/Migrations/20260224194145_InitialCreate.Designer.cs similarity index 100% rename from Migrations/20260224194145_InitialCreate.Designer.cs rename to kilozdazolik.DocumentProcessor/Migrations/20260224194145_InitialCreate.Designer.cs diff --git a/Migrations/20260224194145_InitialCreate.cs b/kilozdazolik.DocumentProcessor/Migrations/20260224194145_InitialCreate.cs similarity index 100% rename from Migrations/20260224194145_InitialCreate.cs rename to kilozdazolik.DocumentProcessor/Migrations/20260224194145_InitialCreate.cs diff --git a/Migrations/PhoneBookContextModelSnapshot.cs b/kilozdazolik.DocumentProcessor/Migrations/PhoneBookContextModelSnapshot.cs similarity index 100% rename from Migrations/PhoneBookContextModelSnapshot.cs rename to kilozdazolik.DocumentProcessor/Migrations/PhoneBookContextModelSnapshot.cs diff --git a/Models/Category.cs b/kilozdazolik.DocumentProcessor/Models/Category.cs similarity index 100% rename from Models/Category.cs rename to kilozdazolik.DocumentProcessor/Models/Category.cs diff --git a/Models/Contact.cs b/kilozdazolik.DocumentProcessor/Models/Contact.cs similarity index 100% rename from Models/Contact.cs rename to kilozdazolik.DocumentProcessor/Models/Contact.cs diff --git a/Models/Dtos.cs b/kilozdazolik.DocumentProcessor/Models/Dtos.cs similarity index 100% rename from Models/Dtos.cs rename to kilozdazolik.DocumentProcessor/Models/Dtos.cs diff --git a/Models/ReportScheduler.cs b/kilozdazolik.DocumentProcessor/Models/ReportScheduler.cs similarity index 100% rename from Models/ReportScheduler.cs rename to kilozdazolik.DocumentProcessor/Models/ReportScheduler.cs diff --git a/Program.cs b/kilozdazolik.DocumentProcessor/Program.cs similarity index 100% rename from Program.cs rename to kilozdazolik.DocumentProcessor/Program.cs diff --git a/Services/ExportService.cs b/kilozdazolik.DocumentProcessor/Services/ExportService.cs similarity index 100% rename from Services/ExportService.cs rename to kilozdazolik.DocumentProcessor/Services/ExportService.cs diff --git a/Services/ImportService.cs b/kilozdazolik.DocumentProcessor/Services/ImportService.cs similarity index 100% rename from Services/ImportService.cs rename to kilozdazolik.DocumentProcessor/Services/ImportService.cs diff --git a/Services/Interfaces/IExportService.cs b/kilozdazolik.DocumentProcessor/Services/Interfaces/IExportService.cs similarity index 100% rename from Services/Interfaces/IExportService.cs rename to kilozdazolik.DocumentProcessor/Services/Interfaces/IExportService.cs diff --git a/Services/Interfaces/IImportService.cs b/kilozdazolik.DocumentProcessor/Services/Interfaces/IImportService.cs similarity index 100% rename from Services/Interfaces/IImportService.cs rename to kilozdazolik.DocumentProcessor/Services/Interfaces/IImportService.cs diff --git a/Services/Interfaces/IReportService.cs b/kilozdazolik.DocumentProcessor/Services/Interfaces/IReportService.cs similarity index 100% rename from Services/Interfaces/IReportService.cs rename to kilozdazolik.DocumentProcessor/Services/Interfaces/IReportService.cs diff --git a/Services/ReportService.cs b/kilozdazolik.DocumentProcessor/Services/ReportService.cs similarity index 100% rename from Services/ReportService.cs rename to kilozdazolik.DocumentProcessor/Services/ReportService.cs diff --git a/Services/WorkerService.cs b/kilozdazolik.DocumentProcessor/Services/WorkerService.cs similarity index 100% rename from Services/WorkerService.cs rename to kilozdazolik.DocumentProcessor/Services/WorkerService.cs diff --git a/UI.cs b/kilozdazolik.DocumentProcessor/UI.cs similarity index 100% rename from UI.cs rename to kilozdazolik.DocumentProcessor/UI.cs diff --git a/appsettings.json b/kilozdazolik.DocumentProcessor/appsettings.json similarity index 100% rename from appsettings.json rename to kilozdazolik.DocumentProcessor/appsettings.json diff --git a/kilozdazolik.DocumentProcessor.csproj b/kilozdazolik.DocumentProcessor/kilozdazolik.DocumentProcessor.csproj similarity index 100% rename from kilozdazolik.DocumentProcessor.csproj rename to kilozdazolik.DocumentProcessor/kilozdazolik.DocumentProcessor.csproj diff --git a/kilozdazolik.DocumentProcessor/kilozdazolik.DocumentProcessor.slnx b/kilozdazolik.DocumentProcessor/kilozdazolik.DocumentProcessor.slnx new file mode 100644 index 0000000..28a61c1 --- /dev/null +++ b/kilozdazolik.DocumentProcessor/kilozdazolik.DocumentProcessor.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/kilozdazolik.DocumentProcessorTests/ImportServiceTests.cs b/kilozdazolik.DocumentProcessorTests/ImportServiceTests.cs new file mode 100644 index 0000000..65151ca --- /dev/null +++ b/kilozdazolik.DocumentProcessorTests/ImportServiceTests.cs @@ -0,0 +1,78 @@ +using Moq; +using Microsoft.EntityFrameworkCore; +using kilozdazolik.DocumentProcessor.Services; +using kilozdazolik.DocumentProcessor.Data; +using kilozdazolik.DocumentProcessor.Models; +using Microsoft.Extensions.Logging; + +public class ImportServiceTests +{ + private PhoneBookContext GetInMemoryContext() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + return new PhoneBookContext(options); + } + + [Fact] + public async Task ValidateAndMap_ShouldSkipRecord_WhenNameIsEmpty() + { + // Arrange + var context = GetInMemoryContext(); + + var category = new Category { Name = "Work" }; + context.Categories.Add(category); + await context.SaveChangesAsync(); + + var loggerMock = new Mock>(); + var service = new ImportService(context, loggerMock.Object); + + var dtos = new List + { + new Dtos.ContactDto("", "test@test.com", "123456", "Work") + }; + + // Act + await service.ValidateAndImportAsync(dtos); + + // Assert + var count = await context.Contacts.CountAsync(); + Assert.Equal(0, count); + } + + [Fact] + public async Task ValidateAndImportAsync_ShouldAllowEmptyEmail_WhenNameAndPhoneAreValid() + { + var dbName = Guid.NewGuid().ToString(); + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: dbName) + .Options; + + using var context = new PhoneBookContext(options); + + var category = new Category { Name = "Work" }; + context.Categories.Add(category); + await context.SaveChangesAsync(); + + var loggerMock = new Mock>(); + var service = new ImportService(context, loggerMock.Object); + + var dtos = new List + { + new Dtos.ContactDto("John Doe", "", "123456", "Work") + }; + + // Act + await service.ValidateAndImportAsync(dtos); + + // Assert + var contact = await context.Contacts + .Include(c => c.Category) + .FirstOrDefaultAsync(c => c.Name == "John Doe"); + + Assert.NotNull(contact); + Assert.Equal("123456", contact.PhoneNumber); + Assert.Equal("", contact.Email); + } +} \ No newline at end of file diff --git a/kilozdazolik.DocumentProcessorTests/ReportSchedulerTests.cs b/kilozdazolik.DocumentProcessorTests/ReportSchedulerTests.cs new file mode 100644 index 0000000..3487c54 --- /dev/null +++ b/kilozdazolik.DocumentProcessorTests/ReportSchedulerTests.cs @@ -0,0 +1,21 @@ +using kilozdazolik.DocumentProcessor.Models; + +public class ReportSchedulerTests +{ + [Fact] + public void Scheduler_ShouldUpdateCorrectly_WhenModified() + { + // Arrange + var scheduler = new ReportScheduler(); + + // Act + scheduler.IsEnabled = true; + scheduler.IntervalMinutes = 30; + scheduler.OutputPath = "new_report.pdf"; + + // Assert + Assert.True(scheduler.IsEnabled); + Assert.Equal(30, scheduler.IntervalMinutes); + Assert.Equal("new_report.pdf", scheduler.OutputPath); + } +} \ No newline at end of file diff --git a/kilozdazolik.DocumentProcessorTests/kilozdazolik.DocumentProcessorTests.csproj b/kilozdazolik.DocumentProcessorTests/kilozdazolik.DocumentProcessorTests.csproj new file mode 100644 index 0000000..c94e441 --- /dev/null +++ b/kilozdazolik.DocumentProcessorTests/kilozdazolik.DocumentProcessorTests.csproj @@ -0,0 +1,33 @@ + + + + net10.0 + enable + enable + false + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + \ No newline at end of file diff --git a/phonebook.db b/phonebook.db deleted file mode 100644 index 5a99688646c72b8616adc02500f8e639b71a088b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeI*?Qhyf90%|_n?OlN+)0~IRYkivo75-`vmFQ}+lw+dI$B~Vpd*b12d{3X>8R0Zs(bB{*Y~ZCKWvvQ-+tl^ zURJx&t}CS`FSaZBrd?W=dIcK zv-^CX6(6?hRZ}#x!_C&MQLU!s+A3@DJyFxBw&*49T2AkR*BS?2-!_ljNA}=?BhC!{ zTz^L`ob7dG((8!dvCl?AWtDo9SMQqPSf*)NlUI0?nUxQJt^ zxLYcgd1`U3u@FzMuhac7D8uJHu_kH{?dL~!zhlpA#-~Cw^Wx3Ekh~>It{oe(M0#a~ zo_u&3=y880JzJQnX}Zf%xW{wwHR3&P;=e^)FdXkmiF7_sPwHW(+ihOiah?n;-|_mx zPo1GJPKUWW<>{f!Jw^m#G^%l(!8D$lb+dE$f{M;e=xClc24455<9}ukhN81G)rz52 z<#_swJSE|2K8l)kUweJYc>p&45nnz3PInO@k=>f5>787;4CoBGzerfq9lu>PmY4@BIMAOHafKmY;| zfB*y_009U<00I!WYyuxpnO_f<|C(WB^+Hz9=GxW1<2zQbG_b|Lis)KK%WN)+aZ%-G zB5p_!fB*y_009U<00Izz00bZa0SH`Df%jydeAQn8g3teB%J)S1OZip#No0^9009U< z00Izz00bZa0SG_<0uZJgJWa|nxn8kcr}uJElF5zAz_I(?;iC9vz>Tu~ z*gER@!TMiPej&;q;sFT)5P$##AOHafKmY;|fB*y_0D(&?@RlSm)9Cu5u_Y?6(8(nz zW2c}0XNmHi@~v_xF4T_%0SG_<0uX=z1Rwwb2tWV=5P(1^kd4L3>PM@z*tJ|%@dm-Q z^+h%#end}X@7OQImG*mfrsKJWp3`$iUN7i|tjlq-Qdmvi^A7s#p7`O6;i1T74ujlG znQU$=e_NC>q_7ALi|o3Ne-IWK2D!OK3R}_!jg#fvsF|{7hb6k6J@5FR{j(jBnUtN0uX=z1Rwwb2tWV=5P$##F0;UbOsO0T9wjLzC#fvU$>hJNtO(lx From cee5d08cf41676766922fdd2ade5da326d63f387 Mon Sep 17 00:00:00 2001 From: kilozdazolik Date: Fri, 6 Mar 2026 18:45:28 +0100 Subject: [PATCH 3/3] removed unused usings --- kilozdazolik.DocumentProcessor/Models/Dtos.cs | 6 +----- .../Services/Interfaces/IExportService.cs | 6 +----- .../Services/Interfaces/IImportService.cs | 7 +------ 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/kilozdazolik.DocumentProcessor/Models/Dtos.cs b/kilozdazolik.DocumentProcessor/Models/Dtos.cs index 7c48814..65f8a47 100644 --- a/kilozdazolik.DocumentProcessor/Models/Dtos.cs +++ b/kilozdazolik.DocumentProcessor/Models/Dtos.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace kilozdazolik.DocumentProcessor.Models +namespace kilozdazolik.DocumentProcessor.Models { public class Dtos { diff --git a/kilozdazolik.DocumentProcessor/Services/Interfaces/IExportService.cs b/kilozdazolik.DocumentProcessor/Services/Interfaces/IExportService.cs index b4deb3b..640e6c0 100644 --- a/kilozdazolik.DocumentProcessor/Services/Interfaces/IExportService.cs +++ b/kilozdazolik.DocumentProcessor/Services/Interfaces/IExportService.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace kilozdazolik.DocumentProcessor.Services.Interfaces +namespace kilozdazolik.DocumentProcessor.Services.Interfaces { public interface IExportService { diff --git a/kilozdazolik.DocumentProcessor/Services/Interfaces/IImportService.cs b/kilozdazolik.DocumentProcessor/Services/Interfaces/IImportService.cs index d374139..c571db6 100644 --- a/kilozdazolik.DocumentProcessor/Services/Interfaces/IImportService.cs +++ b/kilozdazolik.DocumentProcessor/Services/Interfaces/IImportService.cs @@ -1,9 +1,4 @@ -using kilozdazolik.DocumentProcessor.Models; -using System; -using System.Collections.Generic; -using System.Text; - -namespace kilozdazolik.DocumentProcessor.Services.Interfaces +namespace kilozdazolik.DocumentProcessor.Services.Interfaces { public interface IImportService {