Private
Public Access
1
0

Fix .gitignore excluding frontend plugins and test files

The broad plugins/ and imports/ patterns were matching directories
at all levels, excluding frontend/src/plugins/vuetify.ts,
frontend/src/views/imports/, frontend/src/views/plugins/, and
src/Purrse.Tests/Plugins/. Changed to /plugins/ and /imports/
to only match root-level directories.

This was the root cause of the Docker build failure - vuetify.ts
was missing from the repo.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Catherine Renelle
2026-02-08 10:04:19 -05:00
parent 79821e3e04
commit 524176faec
7 changed files with 258 additions and 3 deletions
+3 -3
View File
@@ -35,6 +35,6 @@ docker-compose.override.yml
# Claude Code # Claude Code
.claude/ .claude/
# Plugins and imports (runtime data) # Plugins and imports (runtime data, root-level only)
plugins/ /plugins/
imports/ /imports/
+40
View File
@@ -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',
}
}
}
}
})
@@ -0,0 +1,42 @@
<template>
<div>
<h1 class="text-h4 mb-4">Import Transactions</h1>
<v-card>
<v-card-text>
<v-file-input v-model="file" label="Select file to import" accept=".ofx,.qfx,.csv,.qif" prepend-icon="mdi-upload" />
<v-select v-model="selectedAccount" label="Target Account" :items="accounts" item-title="name" item-value="id" />
<v-btn color="primary" :disabled="!file || !selectedAccount" @click="uploadFile" :loading="uploading">Import</v-btn>
</v-card-text>
</v-card>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useAccountsStore } from '@/stores/accounts'
import api from '@/services/api'
const accountsStore = useAccountsStore()
const accounts = ref(accountsStore.accounts)
const file = ref<File | null>(null)
const selectedAccount = ref('')
const uploading = ref(false)
onMounted(async () => {
await accountsStore.fetchAccounts()
accounts.value = accountsStore.accounts
})
async function uploadFile() {
if (!file.value || !selectedAccount.value) return
uploading.value = true
try {
const formData = new FormData()
formData.append('file', file.value)
await api.post(`/imports/upload?accountId=${selectedAccount.value}`, formData, { headers: { 'Content-Type': 'multipart/form-data' } })
file.value = null
} finally {
uploading.value = false
}
}
</script>
@@ -0,0 +1,6 @@
<template>
<div>
<h1 class="text-h4 mb-4">Plugins</h1>
<v-card><v-card-text><p class="text-medium-emphasis">Plugin marketplace coming in Phase 5. Extend Purrse with community-built integrations and features.</p></v-card-text></v-card>
</div>
</template>
@@ -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);
}
}
@@ -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 = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<OFX>
<BANKMSGSRSV1>
<STMTTRNRS>
<STMTRS>
<BANKACCTFROM>
<ACCTID>123456789</ACCTID>
</BANKACCTFROM>
<BANKTRANLIST>
<DTSTART>20240101</DTSTART>
<DTEND>20240131</DTEND>
<STMTTRN>
<TRNTYPE>DEBIT</TRNTYPE>
<DTPOSTED>20240115</DTPOSTED>
<TRNAMT>-42.50</TRNAMT>
<FITID>20240115001</FITID>
<NAME>WALMART STORE #1234</NAME>
<MEMO>Purchase</MEMO>
</STMTTRN>
<STMTTRN>
<TRNTYPE>CREDIT</TRNTYPE>
<DTPOSTED>20240120</DTPOSTED>
<TRNAMT>1500.00</TRNAMT>
<FITID>20240120001</FITID>
<NAME>DIRECT DEPOSIT</NAME>
</STMTTRN>
</BANKTRANLIST>
</STMTRS>
</STMTTRNRS>
</BANKMSGSRSV1>
</OFX>";
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();
}
}
@@ -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);
}
}