From ac29f0bf986fa84f9772c2d72106f9d5ed248e83 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Fri, 1 Nov 2024 00:11:26 +0800 Subject: [PATCH] read and check ofx 2.x file header --- pkg/converters/ofx/ofx_data_reader.go | 108 ++- pkg/converters/ofx/ofx_data_reader_test.go | 188 +++- .../ofx/ofx_transaction_data_file_importer.go | 2 +- ...ofx_transaction_data_file_importer_test.go | 818 +++++++++--------- 4 files changed, 651 insertions(+), 465 deletions(-) diff --git a/pkg/converters/ofx/ofx_data_reader.go b/pkg/converters/ofx/ofx_data_reader.go index 7908d51a..5030b8b2 100644 --- a/pkg/converters/ofx/ofx_data_reader.go +++ b/pkg/converters/ofx/ofx_data_reader.go @@ -1,17 +1,25 @@ package ofx import ( + "bufio" "bytes" "encoding/xml" + "regexp" + "strings" "golang.org/x/net/html/charset" "github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/errs" + "github.com/mayswind/ezbookkeeping/pkg/log" ) +var ofx2HeaderPattern = regexp.MustCompile("<\\?OFX( +[A-Z]+=\"[^=]*\")* *\\?>") +var ofx2HeaderAttributePattern = regexp.MustCompile(" +([A-Z]+)=\"([^=]*)\"") + // ofxFileReader defines the structure of open financial exchange (ofx) file reader type ofxFileReader struct { + fileHeader *ofxFileHeader xmlDecoder *xml.Decoder } @@ -25,27 +33,97 @@ func (r *ofxFileReader) read(ctx core.Context) (*ofxFile, error) { return nil, err } + file.FileHeader = r.fileHeader + return file, nil } -func createNewOFXFileReader(data []byte) (*ofxFileReader, error) { - if len(data) > 5 && data[0] == 0x3C && data[1] == 0x3F && data[2] == 0x78 && data[3] == 0x6D && data[4] == 0x6C { // ofx 2.x starts with 13 && string(data[0:13]) == "OFXHEADER:100" { // ofx 1.x starts with OFXHEADER:100 +func createNewOFXFileReader(ctx core.Context, data []byte) (*ofxFileReader, error) { + if len(data) > 5 && string(data[0:5]) == " 10 && string(data[0:10]) == "OFXHEADER:" { // ofx 1.x starts with OFXHEADER: } else if len(data) > 5 && string(data[0:5]) == "" { // no ofx header - xmlDecoder := xml.NewDecoder(bytes.NewReader(data)) - xmlDecoder.CharsetReader = charset.NewReaderLabel - - return &ofxFileReader{ - xmlDecoder: xmlDecoder, - }, nil + return createNewOFX2FileReader(ctx, data, false) } return nil, errs.ErrInvalidOFXFile } + +func createNewOFX2FileReader(ctx core.Context, data []byte, withHeader bool) (*ofxFileReader, error) { + var fileHeader *ofxFileHeader = nil + var err error + + if withHeader { + fileHeader, err = readOFX2FileHeader(ctx, data) + + if err != nil { + return nil, err + } + + if fileHeader.OFXDeclarationVersion != ofxVersion2 { + log.Errorf(ctx, "[ofx_data_reader.createNewOFX2FileReader] cannot parse ofx 2.x file header, because declaration version is \"%s\"", fileHeader.OFXDeclarationVersion) + return nil, errs.ErrInvalidOFXFile + } + } + + xmlDecoder := xml.NewDecoder(bytes.NewReader(data)) + xmlDecoder.CharsetReader = charset.NewReaderLabel + + return &ofxFileReader{ + fileHeader: fileHeader, + xmlDecoder: xmlDecoder, + }, nil +} + +func readOFX2FileHeader(ctx core.Context, data []byte) (fileHeader *ofxFileHeader, err error) { + reader := bytes.NewReader(data) + scanner := bufio.NewScanner(reader) + fileHeader = &ofxFileHeader{} + headerLine := "" + + for scanner.Scan() { + line := scanner.Text() + + ofxHeaderStartIndex := strings.Index(line, "= 0 { + headerLine = ofx2HeaderPattern.FindString(line) + break + } + } + + if headerLine == "" { + log.Errorf(ctx, "[ofx_data_reader.readOFX2FileHeader] cannot find ofx 2.x file header") + return nil, errs.ErrInvalidOFXFile + } + + headerAttributes := ofx2HeaderAttributePattern.FindAllStringSubmatch(headerLine, -1) + + for _, attributeItems := range headerAttributes { + if len(attributeItems) != 3 { + log.Warnf(ctx, "[ofx_data_reader.readOFX2FileHeader] cannot parse line in ofx 2.x file header, because item is \"%s\"", attributeItems) + continue + } + + name := attributeItems[1] + value := attributeItems[2] + + if name == "OFXHEADER" { + fileHeader.OFXDeclarationVersion = oFXDeclarationVersion(value) + } else if name == "VERSION" { + fileHeader.OFXDataVersion = value + } else if name == "SECURITY" { + fileHeader.Security = value + } else if name == "OLDFILEUID" { + fileHeader.OldFileUid = value + } else if name == "NEWFILEUID" { + fileHeader.NewFileUid = value + } else { + log.Warnf(ctx, "[ofx_data_reader.readOFX2FileHeader] cannot parse unknown header line in ofx 2.x file header, because item is \"%s\"", attributeItems) + continue + } + } + + return fileHeader, nil +} diff --git a/pkg/converters/ofx/ofx_data_reader_test.go b/pkg/converters/ofx/ofx_data_reader_test.go index 2728ed08..20e4cdc6 100644 --- a/pkg/converters/ofx/ofx_data_reader_test.go +++ b/pkg/converters/ofx/ofx_data_reader_test.go @@ -6,31 +6,32 @@ import ( "github.com/stretchr/testify/assert" "github.com/mayswind/ezbookkeeping/pkg/core" + "github.com/mayswind/ezbookkeeping/pkg/errs" ) func TestCreateNewOFXFileReader_OFX2(t *testing.T) { context := core.NewNullContext() - reader, err := createNewOFXFileReader([]byte( - "" + - "" + - "" + - " " + - " " + - " " + - " CNY" + - " " + - " 123" + - " " + - " " + - " " + - " DEP" + - " 20240901012345.000[+8:CST]" + - " 123.45" + - " " + - " " + - " " + - " " + - " " + + reader, err := createNewOFXFileReader(context, []byte( + "\n"+ + "\n"+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901012345.000[+8:CST]\n"+ + " 123.45\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ "")) assert.Nil(t, err) @@ -38,6 +39,14 @@ func TestCreateNewOFXFileReader_OFX2(t *testing.T) { ofxFile, err := reader.read(context) assert.Nil(t, err) assert.NotNil(t, ofxFile) + + assert.NotNil(t, ofxFile.FileHeader) + assert.Equal(t, ofxVersion2, ofxFile.FileHeader.OFXDeclarationVersion) + assert.Equal(t, "211", ofxFile.FileHeader.OFXDataVersion) + assert.Equal(t, "NONE", ofxFile.FileHeader.Security) + assert.Equal(t, "NONE", ofxFile.FileHeader.OldFileUid) + assert.Equal(t, "NONE", ofxFile.FileHeader.NewFileUid) + assert.NotNil(t, ofxFile.BankMessageResponseV1) assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse) assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse) @@ -53,27 +62,124 @@ func TestCreateNewOFXFileReader_OFX2(t *testing.T) { assert.Equal(t, "123.45", ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions[0].Amount) } +func TestCreateNewOFXFileReader_OFX2WithoutBreakLine(t *testing.T) { + context := core.NewNullContext() + reader, err := createNewOFXFileReader(context, []byte( + ""+ + ""+ + ""+ + " "+ + " "+ + " "+ + " CNY"+ + " "+ + " 123"+ + " "+ + " "+ + " "+ + " DEP"+ + " 20240901012345.000[+8:CST]"+ + " 123.45"+ + " "+ + " "+ + " "+ + " "+ + " "+ + "")) + + assert.Nil(t, err) + + ofxFile, err := reader.read(context) + assert.Nil(t, err) + assert.NotNil(t, ofxFile) + + assert.NotNil(t, ofxFile.FileHeader) + assert.Equal(t, ofxVersion2, ofxFile.FileHeader.OFXDeclarationVersion) + assert.Equal(t, "211", ofxFile.FileHeader.OFXDataVersion) + assert.Equal(t, "NONE", ofxFile.FileHeader.Security) + assert.Equal(t, "NONE", ofxFile.FileHeader.OldFileUid) + assert.Equal(t, "NONE", ofxFile.FileHeader.NewFileUid) + + assert.NotNil(t, ofxFile.BankMessageResponseV1) + assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse) + assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse) + + assert.Equal(t, "CNY", ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.DefaultCurrency) + + assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.AccountFrom) + assert.Equal(t, "123", ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.AccountFrom.AccountId) + + assert.Equal(t, 1, len(ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions)) + assert.Equal(t, ofxDepositTransaction, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions[0].TransactionType) + assert.Equal(t, "20240901012345.000[+8:CST]", ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions[0].PostedDate) + assert.Equal(t, "123.45", ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse.TransactionList.StatementTransactions[0].Amount) +} + +func TestCreateNewOFXFileReader_OFX2WithoutOFXHeader(t *testing.T) { + context := core.NewNullContext() + _, err := createNewOFXFileReader(context, []byte( + ""+ + ""+ + "")) + + assert.EqualError(t, err, errs.ErrInvalidOFXFile.Message) +} + +func TestCreateNewOFXFileReader_OFX2WithInvalidHeaderVersion(t *testing.T) { + context := core.NewNullContext() + _, err := createNewOFXFileReader(context, []byte( + ""+ + ""+ + ""+ + "")) + + assert.EqualError(t, err, errs.ErrInvalidOFXFile.Message) +} + +func TestCreateNewOFXFileReader_OFX2WithInvalidHeader(t *testing.T) { + context := core.NewNullContext() + _, err := createNewOFXFileReader(context, []byte( + ""+ + ""+ + ""+ + "")) + + _, err = createNewOFXFileReader(context, []byte( + ""+ + ""+ + ""+ + "")) + + assert.EqualError(t, err, errs.ErrInvalidOFXFile.Message) + _, err = createNewOFXFileReader(context, []byte( + ""+ + ""+ + ""+ + "")) + assert.EqualError(t, err, errs.ErrInvalidOFXFile.Message) +} + func TestCreateNewOFXFileReader_OFX2WithoutAnyHeader(t *testing.T) { context := core.NewNullContext() - reader, err := createNewOFXFileReader([]byte( - "" + - " " + - " " + - " " + - " CNY" + - " " + - " 123" + - " " + - " " + - " " + - " DEP" + - " 20240901012345.000[+8:CST]" + - " 123.45" + - " " + - " " + - " " + - " " + - " " + + reader, err := createNewOFXFileReader(context, []byte( + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901012345.000[+8:CST]\n"+ + " 123.45\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ "")) assert.Nil(t, err) @@ -81,6 +187,8 @@ func TestCreateNewOFXFileReader_OFX2WithoutAnyHeader(t *testing.T) { ofxFile, err := reader.read(context) assert.Nil(t, err) assert.NotNil(t, ofxFile) + assert.Nil(t, ofxFile.FileHeader) + assert.NotNil(t, ofxFile.BankMessageResponseV1) assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse) assert.NotNil(t, ofxFile.BankMessageResponseV1.StatementTransactionResponse.StatementResponse) diff --git a/pkg/converters/ofx/ofx_transaction_data_file_importer.go b/pkg/converters/ofx/ofx_transaction_data_file_importer.go index b56c37fc..74f7485e 100644 --- a/pkg/converters/ofx/ofx_transaction_data_file_importer.go +++ b/pkg/converters/ofx/ofx_transaction_data_file_importer.go @@ -24,7 +24,7 @@ var ( // ParseImportedData returns the imported data by parsing the open financial exchange (ofx) file transaction data func (c *ofxTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { - ofxDataReader, err := createNewOFXFileReader(data) + ofxDataReader, err := createNewOFXFileReader(ctx, data) if err != nil { return nil, nil, nil, nil, nil, nil, err diff --git a/pkg/converters/ofx/ofx_transaction_data_file_importer_test.go b/pkg/converters/ofx/ofx_transaction_data_file_importer_test.go index bc512e0f..2f2126ab 100644 --- a/pkg/converters/ofx/ofx_transaction_data_file_importer_test.go +++ b/pkg/converters/ofx/ofx_transaction_data_file_importer_test.go @@ -21,61 +21,61 @@ func TestOFXTransactionDataFileParseImportedData_MinimumValidData(t *testing.T) } allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " 123"+ - " "+ - " "+ - " "+ - " DEP"+ - " 20240901012345.000[+8:CST]"+ - " 123.45"+ - " "+ - " "+ - " CHECK"+ - " 20240901123456.000[+8:CST]"+ - " -0.12"+ - " "+ - " "+ - " XFER"+ - " 20240901225959.000[+8:CST]"+ - " -1.00"+ - " "+ - " "+ - " XFER"+ - " 20240901235959.000[+8:CST]"+ - " 2.00"+ - " "+ - " "+ - " "+ - " "+ - " "+ - " "+ - " "+ - " "+ - " USD"+ - " "+ - " 456"+ - " "+ - " "+ - " "+ - " ATM"+ - " 20240902012345.000[+8:CST]"+ - " 1.23"+ - " "+ - " "+ - " POS"+ - " 20240902123456.000[+8:CST]"+ - " -0.01"+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901012345.000[+8:CST]\n"+ + " 123.45\n"+ + " \n"+ + " \n"+ + " CHECK\n"+ + " 20240901123456.000[+8:CST]\n"+ + " -0.12\n"+ + " \n"+ + " \n"+ + " XFER\n"+ + " 20240901225959.000[+8:CST]\n"+ + " -1.00\n"+ + " \n"+ + " \n"+ + " XFER\n"+ + " 20240901235959.000[+8:CST]\n"+ + " 2.00\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " USD\n"+ + " \n"+ + " 456\n"+ + " \n"+ + " \n"+ + " \n"+ + " ATM\n"+ + " 20240902012345.000[+8:CST]\n"+ + " 1.23\n"+ + " \n"+ + " \n"+ + " POS\n"+ + " 20240902123456.000[+8:CST]\n"+ + " -0.01\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.Nil(t, err) @@ -167,47 +167,47 @@ func TestOFXTransactionDataFileParseImportedData_ParseAccountTo(t *testing.T) { } allNewTransactions, allNewAccounts, _, _, _, _, err := converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " 123"+ - " "+ - " "+ - " "+ - " XFER"+ - " 20240901012345.000[+8:CST]"+ - " -123.45"+ - " "+ - " 456"+ - " "+ - " "+ - " "+ - " "+ - " "+ - " "+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " 456"+ - " "+ - " "+ - " "+ - " XFER"+ - " 20240902012345.000[+8:CST]"+ - " -1.23"+ - " "+ - " 789"+ - " "+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " XFER\n"+ + " 20240901012345.000[+8:CST]\n"+ + " -123.45\n"+ + " \n"+ + " 456\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 456\n"+ + " \n"+ + " \n"+ + " \n"+ + " XFER\n"+ + " 20240902012345.000[+8:CST]\n"+ + " -1.23\n"+ + " \n"+ + " 789\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.Nil(t, err) @@ -250,49 +250,49 @@ func TestOFXTransactionDataFileParseImportedData_ParseValidTransactionTime(t *te } allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " 123"+ - " "+ - " "+ - " "+ - " DEP"+ - " 20240901"+ - " 123.45"+ - " "+ - " "+ - " DEP"+ - " 20240901123456"+ - " 123.45"+ - " "+ - " "+ - " DEP"+ - " 20240901123456.789"+ - " 123.45"+ - " "+ - " "+ - " DEP"+ - " 20240901125959.000[-3]"+ - " 123.45"+ - " "+ - " "+ - " DEP"+ - " 20240901122959.000[-3.5]"+ - " 123.45"+ - " "+ - " "+ - " DEP"+ - " 20240902030405.000[0]"+ - " 123.45"+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901\n"+ + " 123.45\n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901123456\n"+ + " 123.45\n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901123456.789\n"+ + " 123.45\n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901125959.000[-3]\n"+ + " 123.45\n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901122959.000[-3.5]\n"+ + " 123.45\n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240902030405.000[0]\n"+ + " 123.45\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.Nil(t, err) @@ -317,90 +317,90 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidTransactionTime(t * } _, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " 123"+ - " "+ - " "+ - " "+ - " DEP"+ - " 2024"+ - " 123.45"+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 2024\n"+ + " 123.45\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) _, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " 123"+ - " "+ - " "+ - " "+ - " DEP"+ - " 2024-09-01"+ - " 123.45"+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 2024-09-01\n"+ + " 123.45\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) _, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " 123"+ - " "+ - " "+ - " "+ - " DEP"+ - " 202491"+ - " 123.45"+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 202491\n"+ + " 123.45\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) _, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " 123"+ - " "+ - " "+ - " "+ - " DEP"+ - " 20240901 12:34:56"+ - " 123.45"+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901 12:34:56\n"+ + " 123.45\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message) } @@ -415,24 +415,24 @@ func TestOFXTransactionDataFileParseImportedData_ParseAmount_CommaAsDecimalPoint } allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " 123"+ - " "+ - " "+ - " "+ - " DEP"+ - " 20240901012345.000[+8:CST]"+ - " 123,45"+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901012345.000[+8:CST]\n"+ + " 123,45\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.Nil(t, err) @@ -451,24 +451,24 @@ func TestOFXTransactionDataFileParseImportedData_ParseInvalidAmount(t *testing.T } _, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " 123"+ - " "+ - " "+ - " "+ - " DEP"+ - " 20240901012345.000[+8:CST]"+ - " 123 45"+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901012345.000[+8:CST]\n"+ + " 123 45\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.EqualError(t, err, errs.ErrAmountInvalid.Message) } @@ -483,25 +483,25 @@ func TestOFXTransactionDataFileParseImportedData_ParseTransactionCurrency(t *tes } allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " 123"+ - " "+ - " "+ - " "+ - " DEP"+ - " 20240901012345.000[+8:CST]"+ - " 123.45"+ - " USD"+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901012345.000[+8:CST]\n"+ + " 123.45\n"+ + " USD\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.Nil(t, err) @@ -519,26 +519,26 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T) } allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " 123"+ - " "+ - " "+ - " "+ - " DEP"+ - " 20240901012345.000[+8:CST]"+ - " 123.45"+ - " Test"+ - " foo bar\t#test"+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901012345.000[+8:CST]\n"+ + " 123.45\n"+ + " Test\n"+ + " foo bar\t#test\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.Nil(t, err) @@ -546,25 +546,25 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T) assert.Equal(t, "foo bar\t#test", allNewTransactions[0].Comment) allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " 123"+ - " "+ - " "+ - " "+ - " DEP"+ - " 20240901012345.000[+8:CST]"+ - " 123.45"+ - " Test"+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901012345.000[+8:CST]\n"+ + " 123.45\n"+ + " Test\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.Nil(t, err) @@ -572,27 +572,27 @@ func TestOFXTransactionDataFileParseImportedData_ParseDescription(t *testing.T) assert.Equal(t, "Test", allNewTransactions[0].Comment) allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " 123"+ - " "+ - " "+ - " "+ - " DEP"+ - " 20240901012345.000[+8:CST]"+ - " 123.45"+ - " "+ - " Test"+ - " "+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901012345.000[+8:CST]\n"+ + " 123.45\n"+ + " \n"+ + " Test\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.Nil(t, err) @@ -611,21 +611,21 @@ func TestOFXTransactionDataFileParseImportedData_MissingAccountFromNode(t *testi // Missing Posted Date Node _, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " "+ - " DEP"+ - " 20240901012345.000[+8:CST]"+ - " 123.45"+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901012345.000[+8:CST]\n"+ + " 123.45\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.EqualError(t, err, errs.ErrMissingAccountData.Message) } @@ -641,23 +641,23 @@ func TestOFXTransactionDataFileParseImportedData_MissingCurrencyNode(t *testing. // Missing Default Currency Node _, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " "+ - " 123"+ - " "+ - " "+ - " "+ - " DEP"+ - " 20240901012345.000[+8:CST]"+ - " 123.45"+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901012345.000[+8:CST]\n"+ + " 123.45\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.EqualError(t, err, errs.ErrAccountCurrencyInvalid.Message) } @@ -673,67 +673,67 @@ func TestOFXTransactionDataFileParseImportedData_MissingTransactionRequiredNode( // Missing Posted Date Node _, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " 123"+ - " "+ - " "+ - " "+ - " DEP"+ - " 123.45"+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 123.45\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.EqualError(t, err, errs.ErrMissingTransactionTime.Message) // Missing Transaction Type Node _, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " 123"+ - " "+ - " "+ - " "+ - " 20240901012345.000[+8:CST]"+ - " 123.45"+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " 20240901012345.000[+8:CST]\n"+ + " 123.45\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.EqualError(t, err, errs.ErrTransactionTypeInvalid.Message) // Missing Amount Node _, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte( - ""+ - " "+ - " "+ - " "+ - " CNY"+ - " "+ - " 123"+ - " "+ - " "+ - " "+ - " DEP"+ - " 20240901012345.000[+8:CST]"+ - " "+ - " "+ - " "+ - " "+ - " "+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " CNY\n"+ + " \n"+ + " 123\n"+ + " \n"+ + " \n"+ + " \n"+ + " DEP\n"+ + " 20240901012345.000[+8:CST]\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ ""), 0, nil, nil, nil, nil, nil) assert.EqualError(t, err, errs.ErrAmountInvalid.Message) }