diff --git a/.gitignore b/.gitignore index 9f84535..396f155 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,6 @@ docker-compose.override.yml # Claude Code .claude/ -# Plugins and imports (runtime data) -plugins/ -imports/ +# Plugins and imports (runtime data, root-level only) +/plugins/ +/imports/ diff --git a/frontend/src/plugins/vuetify.ts b/frontend/src/plugins/vuetify.ts new file mode 100644 index 0000000..3b850fc --- /dev/null +++ b/frontend/src/plugins/vuetify.ts @@ -0,0 +1,40 @@ +import 'vuetify/styles' +import { createVuetify } from 'vuetify' +import * as components from 'vuetify/components' +import * as directives from 'vuetify/directives' + +export default createVuetify({ + components, + directives, + theme: { + defaultTheme: 'dark', + themes: { + dark: { + dark: true, + colors: { + primary: '#7C4DFF', + secondary: '#FF6D00', + accent: '#00BFA5', + error: '#FF5252', + warning: '#FFC107', + info: '#2196F3', + success: '#4CAF50', + background: '#121212', + surface: '#1E1E1E', + } + }, + light: { + dark: false, + colors: { + primary: '#6200EA', + secondary: '#FF6D00', + accent: '#00BFA5', + error: '#FF5252', + warning: '#FFC107', + info: '#2196F3', + success: '#4CAF50', + } + } + } + } +}) diff --git a/frontend/src/views/imports/ImportsView.vue b/frontend/src/views/imports/ImportsView.vue new file mode 100644 index 0000000..77550c0 --- /dev/null +++ b/frontend/src/views/imports/ImportsView.vue @@ -0,0 +1,42 @@ + + + Import Transactions + + + + + Import + + + + + + diff --git a/frontend/src/views/plugins/PluginsView.vue b/frontend/src/views/plugins/PluginsView.vue new file mode 100644 index 0000000..a264c5a --- /dev/null +++ b/frontend/src/views/plugins/PluginsView.vue @@ -0,0 +1,6 @@ + + + Plugins + Plugin marketplace coming in Phase 5. Extend Purrse with community-built integrations and features. + + diff --git a/src/Purrse.Tests/Plugins/CSVParserTests.cs b/src/Purrse.Tests/Plugins/CSVParserTests.cs new file mode 100644 index 0000000..57f5801 --- /dev/null +++ b/src/Purrse.Tests/Plugins/CSVParserTests.cs @@ -0,0 +1,49 @@ +using FluentAssertions; +using Purrse.Plugins.CSV; + +namespace Purrse.Tests.Plugins; + +public class CSVParserTests +{ + private readonly CSVParserPlugin _parser = new(); + + [Fact] + public void CanParse_CSVFile_ReturnsTrue() + { + _parser.CanParse("transactions.csv", Stream.Null).Should().BeTrue(); + } + + [Fact] + public void CanParse_OFXFile_ReturnsFalse() + { + _parser.CanParse("statement.ofx", Stream.Null).Should().BeFalse(); + } + + [Fact] + public async Task ParseAsync_StandardCSV_ParsesTransactions() + { + var csv = "Date,Description,Amount\n01/15/2024,WALMART,-42.50\n01/20/2024,PAYCHECK,1500.00\n"; + + using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(csv)); + var result = await _parser.ParseAsync(stream, "test.csv"); + + result.Success.Should().BeTrue(); + result.Transactions.Should().HaveCount(2); + result.Transactions[0].PayeeName.Should().Be("WALMART"); + result.Transactions[0].Amount.Should().Be(-42.50m); + } + + [Fact] + public async Task ParseAsync_DebitCreditColumns_ParsesCorrectly() + { + var csv = "Date,Description,Debit,Credit\n01/15/2024,WALMART,42.50,\n01/20/2024,PAYCHECK,,1500.00\n"; + + using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(csv)); + var result = await _parser.ParseAsync(stream, "test.csv"); + + result.Success.Should().BeTrue(); + result.Transactions.Should().HaveCount(2); + result.Transactions[0].Amount.Should().Be(-42.50m); + result.Transactions[1].Amount.Should().Be(1500.00m); + } +} diff --git a/src/Purrse.Tests/Plugins/OFXParserTests.cs b/src/Purrse.Tests/Plugins/OFXParserTests.cs new file mode 100644 index 0000000..becf1f5 --- /dev/null +++ b/src/Purrse.Tests/Plugins/OFXParserTests.cs @@ -0,0 +1,88 @@ +using FluentAssertions; +using Purrse.Plugins.OFX; + +namespace Purrse.Tests.Plugins; + +public class OFXParserTests +{ + private readonly OFXParserPlugin _parser = new(); + + [Fact] + public void CanParse_OFXFile_ReturnsTrue() + { + _parser.CanParse("statement.ofx", Stream.Null).Should().BeTrue(); + } + + [Fact] + public void CanParse_QFXFile_ReturnsTrue() + { + _parser.CanParse("download.qfx", Stream.Null).Should().BeTrue(); + } + + [Fact] + public void CanParse_CSVFile_ReturnsFalse() + { + _parser.CanParse("data.csv", Stream.Null).Should().BeFalse(); + } + + [Fact] + public async Task ParseAsync_ValidXmlOFX_ParsesTransactions() + { + var ofxContent = @" + + + + + + 123456789 + + + 20240101 + 20240131 + + DEBIT + 20240115 + -42.50 + 20240115001 + WALMART STORE #1234 + Purchase + + + CREDIT + 20240120 + 1500.00 + 20240120001 + DIRECT DEPOSIT + + + + + +"; + + using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(ofxContent)); + var result = await _parser.ParseAsync(stream, "test.ofx"); + + result.Success.Should().BeTrue(); + result.Transactions.Should().HaveCount(2); + result.AccountIdentifier.Should().Be("123456789"); + + var debit = result.Transactions[0]; + debit.Amount.Should().Be(-42.50m); + debit.PayeeName.Should().Be("WALMART STORE #1234"); + debit.FitId.Should().Be("20240115001"); + debit.Date.Should().Be(new DateTime(2024, 1, 15)); + + var credit = result.Transactions[1]; + credit.Amount.Should().Be(1500.00m); + } + + [Fact] + public async Task ParseAsync_EmptyStream_ReturnsFailure() + { + using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes("")); + var result = await _parser.ParseAsync(stream, "empty.ofx"); + + result.Success.Should().BeFalse(); + } +} diff --git a/src/Purrse.Tests/Plugins/QIFParserTests.cs b/src/Purrse.Tests/Plugins/QIFParserTests.cs new file mode 100644 index 0000000..bfa72c0 --- /dev/null +++ b/src/Purrse.Tests/Plugins/QIFParserTests.cs @@ -0,0 +1,30 @@ +using FluentAssertions; +using Purrse.Plugins.QIF; + +namespace Purrse.Tests.Plugins; + +public class QIFParserTests +{ + private readonly QIFParserPlugin _parser = new(); + + [Fact] + public void CanParse_QIFFile_ReturnsTrue() + { + _parser.CanParse("data.qif", Stream.Null).Should().BeTrue(); + } + + [Fact] + public async Task ParseAsync_ValidQIF_ParsesTransactions() + { + var qif = "!Type:Bank\nD01/15/2024\nT-42.50\nPWALMART\nMGroceries\n^\nD01/20/2024\nT1500.00\nPDIRECT DEPOSIT\n^\n"; + + using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(qif)); + var result = await _parser.ParseAsync(stream, "test.qif"); + + result.Success.Should().BeTrue(); + result.Transactions.Should().HaveCount(2); + result.Transactions[0].Amount.Should().Be(-42.50m); + result.Transactions[0].PayeeName.Should().Be("WALMART"); + result.Transactions[1].Amount.Should().Be(1500.00m); + } +}
Plugin marketplace coming in Phase 5. Extend Purrse with community-built integrations and features.