mirror of
				https://github.com/aclindsa/ofxgo.git
				synced 2025-11-04 02:03:26 -05:00 
			
		
		
		
	Compare commits
	
		
			37 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					8ad638c7e2 | ||
| 
						 | 
					f19189de45 | ||
| 
						 | 
					677a09295a | ||
| 
						 | 
					f75592381a | ||
| ebf7f5b757 | |||
| 
						 | 
					212fdc731b | ||
| 
						 | 
					66dd37781f | ||
| 
						 | 
					67e527c855 | ||
| f41286cac7 | |||
| 423d460747 | |||
| 3e8a9c5a53 | |||
| 35c7116654 | |||
| 286e619071 | |||
| 9dd9c3bd3f | |||
| 
						 | 
					0f6ceccd86 | ||
| 7691881132 | |||
| 61262b87d8 | |||
| 5e2e3a2bf7 | |||
| 22a6d65b98 | |||
| 77b154695f | |||
| ac09538ec3 | |||
| d8491bed1d | |||
| 1b4f27b31f | |||
| eb35a26986 | |||
| 5c10ac5ea1 | |||
| 94a77ac754 | |||
| de58d3fc0d | |||
| 88e5521348 | |||
| 54666608a4 | |||
| 1cc508c6d3 | |||
| c6a806399a | |||
| 
						 | 
					2a92b29a62 | ||
| 
						 | 
					2fbb276a22 | ||
| 06de7e2af6 | |||
| 977dacfbbd | |||
| ddc674b287 | |||
| 29fc9c20fe | 
@@ -5,12 +5,15 @@ os:
 | 
			
		||||
 | 
			
		||||
go:
 | 
			
		||||
  - 1.9.x
 | 
			
		||||
  - 1.12.x
 | 
			
		||||
  - master
 | 
			
		||||
 | 
			
		||||
script:
 | 
			
		||||
  - go get golang.org/x/tools/cmd/cover
 | 
			
		||||
before_install:
 | 
			
		||||
  # Fetch/build coverage reporting tools
 | 
			
		||||
  - go get github.com/mattn/goveralls
 | 
			
		||||
  - go install github.com/mattn/goveralls
 | 
			
		||||
 | 
			
		||||
script:
 | 
			
		||||
  - go test -v -covermode=count -coverprofile=coverage.out
 | 
			
		||||
 | 
			
		||||
after_script:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										56
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								README.md
									
									
									
									
									
								
							@@ -38,6 +38,62 @@ repository.
 | 
			
		||||
Documentation can be found with the `go doc` tool, or at
 | 
			
		||||
https://godoc.org/github.com/aclindsa/ofxgo
 | 
			
		||||
 | 
			
		||||
## Example Usage
 | 
			
		||||
 | 
			
		||||
The following code snippet demonstrates how to use OFXGo to query and parse
 | 
			
		||||
OFX code from a checking account, printing the balance and returned transactions:
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
client := ofxgo.BasicClient{} // Accept the default Client settings
 | 
			
		||||
 | 
			
		||||
// These values are specific to your bank
 | 
			
		||||
var query ofxgo.Request
 | 
			
		||||
query.URL = "https://secu.example.com/ofx"
 | 
			
		||||
query.Signon.Org = ofxgo.String("SECU")
 | 
			
		||||
query.Signon.Fid = ofxgo.String("1234")
 | 
			
		||||
 | 
			
		||||
// Set your username/password
 | 
			
		||||
query.Signon.UserID = ofxgo.String("username")
 | 
			
		||||
query.Signon.UserPass = ofxgo.String("hunter2")
 | 
			
		||||
 | 
			
		||||
uid, _ := ofxgo.RandomUID() // Handle error in real code
 | 
			
		||||
query.Bank = append(query.Bank, &ofxgo.StatementRequest{
 | 
			
		||||
	TrnUID: *uid,
 | 
			
		||||
	BankAcctFrom: ofxgo.BankAcct{
 | 
			
		||||
		BankID:   ofxgo.String("123456789"),   // Possibly your routing number
 | 
			
		||||
		AcctID:   ofxgo.String("00011122233"), // Possibly your account number
 | 
			
		||||
		AcctType: ofxgo.AcctTypeChecking,
 | 
			
		||||
	},
 | 
			
		||||
	Include: true, // Include transactions (instead of only balance information)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
response, _ := client.Request(&query) // Handle error in real code
 | 
			
		||||
 | 
			
		||||
// Was there an OFX error while processing our request?
 | 
			
		||||
if response.Signon.Status.Code != 0 {
 | 
			
		||||
	meaning, _ := response.Signon.Status.CodeMeaning()
 | 
			
		||||
	fmt.Printf("Nonzero signon status (%d: %s) with message: %s\n", response.Signon.Status.Code, meaning, response.Signon.Status.Message)
 | 
			
		||||
	os.Exit(1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if len(response.Bank) < 1 {
 | 
			
		||||
	fmt.Println("No banking messages received")
 | 
			
		||||
	os.Exit(1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if stmt, ok := response.Bank[0].(*ofxgo.StatementResponse); ok {
 | 
			
		||||
	fmt.Printf("Balance: %s %s (as of %s)\n", stmt.BalAmt, stmt.CurDef, stmt.DtAsOf)
 | 
			
		||||
	fmt.Println("Transactions:")
 | 
			
		||||
	for _, tran := range stmt.BankTranList.Transactions {
 | 
			
		||||
		currency := stmt.CurDef
 | 
			
		||||
		if ok, _ := tran.Currency.Valid(); ok {
 | 
			
		||||
			currency = tran.Currency.CurSym
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Printf("%s %-15s %-11s %s%s%s\n", tran.DtPosted, tran.TrnAmt.String()+" "+currency.String(), tran.TrnType, tran.Name, tran.Payee.Name, tran.Memo)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Requirements
 | 
			
		||||
 | 
			
		||||
OFXGo requires go >= 1.9
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								bank.go
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								bank.go
									
									
									
									
									
								
							@@ -126,8 +126,8 @@ type Transaction struct {
 | 
			
		||||
	ImageData  []ImageData `xml:"IMAGEDATA,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// Only one of Currency and OrigCurrency can ever be Valid() for the same transaction
 | 
			
		||||
	Currency      Currency      `xml:"CURRENCY,omitempty"`      // Represents the currency of TrnAmt (instead of CURDEF in STMTRS) if Valid
 | 
			
		||||
	OrigCurrency  Currency      `xml:"ORIGCURRENCY,omitempty"`  // Represents the currency TrnAmt was converted to STMTRS' CURDEF from if Valid
 | 
			
		||||
	Currency      *Currency     `xml:"CURRENCY,omitempty"`      // Represents the currency of TrnAmt (instead of CURDEF in STMTRS) if Valid
 | 
			
		||||
	OrigCurrency  *Currency     `xml:"ORIGCURRENCY,omitempty"`  // Represents the currency TrnAmt was converted to STMTRS' CURDEF from if Valid
 | 
			
		||||
	Inv401kSource inv401kSource `xml:"INV401KSOURCE,omitempty"` // One of PRETAX, AFTERTAX, MATCH, PROFITSHARING, ROLLOVER, OTHERVEST, OTHERNONVEST (Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST.)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -166,8 +166,13 @@ func (t Transaction) Valid(version ofxVersion) (bool, error) {
 | 
			
		||||
	} else if len(t.ImageData) > 2 {
 | 
			
		||||
		return false, errors.New("Only 2 of ImageData allowed in Transaction")
 | 
			
		||||
	}
 | 
			
		||||
	ok1, _ := t.Currency.Valid()
 | 
			
		||||
	ok2, _ := t.OrigCurrency.Valid()
 | 
			
		||||
	var ok1, ok2 bool
 | 
			
		||||
	if t.Currency != nil {
 | 
			
		||||
		ok1, _ = t.Currency.Valid()
 | 
			
		||||
	}
 | 
			
		||||
	if t.OrigCurrency != nil {
 | 
			
		||||
		ok2, _ = t.OrigCurrency.Valid()
 | 
			
		||||
	}
 | 
			
		||||
	if ok1 && ok2 {
 | 
			
		||||
		return false, errors.New("Currency and OrigCurrency both supplied for Pending Transaction, only one allowed")
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										106
									
								
								bank_test.go
									
									
									
									
									
								
							
							
						
						
									
										106
									
								
								bank_test.go
									
									
									
									
									
								
							@@ -1,7 +1,6 @@
 | 
			
		||||
package ofxgo_test
 | 
			
		||||
package ofxgo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/aclindsa/ofxgo"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -42,24 +41,24 @@ func TestMarshalBankStatementRequest(t *testing.T) {
 | 
			
		||||
	</BANKMSGSRQV1>
 | 
			
		||||
</OFX>`
 | 
			
		||||
 | 
			
		||||
	var client = ofxgo.Client{
 | 
			
		||||
	var client = BasicClient{
 | 
			
		||||
		AppID:       "OFXGO",
 | 
			
		||||
		AppVer:      "0001",
 | 
			
		||||
		SpecVersion: ofxgo.OfxVersion203,
 | 
			
		||||
		SpecVersion: OfxVersion203,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var request ofxgo.Request
 | 
			
		||||
	var request Request
 | 
			
		||||
	request.Signon.UserID = "myusername"
 | 
			
		||||
	request.Signon.UserPass = "Pa$$word"
 | 
			
		||||
	request.Signon.Org = "BNK"
 | 
			
		||||
	request.Signon.Fid = "1987"
 | 
			
		||||
 | 
			
		||||
	statementRequest := ofxgo.StatementRequest{
 | 
			
		||||
	statementRequest := StatementRequest{
 | 
			
		||||
		TrnUID: "123",
 | 
			
		||||
		BankAcctFrom: ofxgo.BankAcct{
 | 
			
		||||
		BankAcctFrom: BankAcct{
 | 
			
		||||
			BankID:   "318398732",
 | 
			
		||||
			AcctID:   "78346129",
 | 
			
		||||
			AcctType: ofxgo.AcctTypeChecking,
 | 
			
		||||
			AcctType: AcctTypeChecking,
 | 
			
		||||
		},
 | 
			
		||||
		Include: true,
 | 
			
		||||
	}
 | 
			
		||||
@@ -68,7 +67,7 @@ func TestMarshalBankStatementRequest(t *testing.T) {
 | 
			
		||||
	request.SetClientFields(&client)
 | 
			
		||||
	// Overwrite the DtClient value set by SetClientFields to time.Now()
 | 
			
		||||
	EST := time.FixedZone("EST", -5*60*60)
 | 
			
		||||
	request.Signon.DtClient = *ofxgo.NewDate(2006, 1, 15, 11, 23, 0, 0, EST)
 | 
			
		||||
	request.Signon.DtClient = *NewDate(2006, 1, 15, 11, 23, 0, 0, EST)
 | 
			
		||||
 | 
			
		||||
	marshalCheckRequest(t, &request, expectedString)
 | 
			
		||||
}
 | 
			
		||||
@@ -87,53 +86,53 @@ NEWFILEUID:NONE
 | 
			
		||||
<OFX>
 | 
			
		||||
	<SIGNONMSGSRQV1>
 | 
			
		||||
		<SONRQ>
 | 
			
		||||
			<DTCLIENT>20060115112300.000[-5:EST]</DTCLIENT>
 | 
			
		||||
			<USERID>myusername</USERID>
 | 
			
		||||
			<USERPASS>Pa$$word</USERPASS>
 | 
			
		||||
			<LANGUAGE>ENG</LANGUAGE>
 | 
			
		||||
			<DTCLIENT>20060115112300.000[-5:EST]
 | 
			
		||||
			<USERID>myusername
 | 
			
		||||
			<USERPASS>Pa$$word
 | 
			
		||||
			<LANGUAGE>ENG
 | 
			
		||||
			<FI>
 | 
			
		||||
				<ORG>BNK</ORG>
 | 
			
		||||
				<FID>1987</FID>
 | 
			
		||||
				<ORG>BNK
 | 
			
		||||
				<FID>1987
 | 
			
		||||
			</FI>
 | 
			
		||||
			<APPID>OFXGO</APPID>
 | 
			
		||||
			<APPVER>0001</APPVER>
 | 
			
		||||
			<APPID>OFXGO
 | 
			
		||||
			<APPVER>0001
 | 
			
		||||
		</SONRQ>
 | 
			
		||||
	</SIGNONMSGSRQV1>
 | 
			
		||||
	<BANKMSGSRQV1>
 | 
			
		||||
		<STMTTRNRQ>
 | 
			
		||||
			<TRNUID>123</TRNUID>
 | 
			
		||||
			<TRNUID>123
 | 
			
		||||
			<STMTRQ>
 | 
			
		||||
				<BANKACCTFROM>
 | 
			
		||||
					<BANKID>318398732</BANKID>
 | 
			
		||||
					<ACCTID>78346129</ACCTID>
 | 
			
		||||
					<ACCTTYPE>CHECKING</ACCTTYPE>
 | 
			
		||||
					<BANKID>318398732
 | 
			
		||||
					<ACCTID>78346129
 | 
			
		||||
					<ACCTTYPE>CHECKING
 | 
			
		||||
				</BANKACCTFROM>
 | 
			
		||||
				<INCTRAN>
 | 
			
		||||
					<INCLUDE>Y</INCLUDE>
 | 
			
		||||
					<INCLUDE>Y
 | 
			
		||||
				</INCTRAN>
 | 
			
		||||
			</STMTRQ>
 | 
			
		||||
		</STMTTRNRQ>
 | 
			
		||||
	</BANKMSGSRQV1>
 | 
			
		||||
</OFX>`
 | 
			
		||||
 | 
			
		||||
	var client = ofxgo.Client{
 | 
			
		||||
	var client = BasicClient{
 | 
			
		||||
		AppID:       "OFXGO",
 | 
			
		||||
		AppVer:      "0001",
 | 
			
		||||
		SpecVersion: ofxgo.OfxVersion103,
 | 
			
		||||
		SpecVersion: OfxVersion103,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var request ofxgo.Request
 | 
			
		||||
	var request Request
 | 
			
		||||
	request.Signon.UserID = "myusername"
 | 
			
		||||
	request.Signon.UserPass = "Pa$$word"
 | 
			
		||||
	request.Signon.Org = "BNK"
 | 
			
		||||
	request.Signon.Fid = "1987"
 | 
			
		||||
 | 
			
		||||
	statementRequest := ofxgo.StatementRequest{
 | 
			
		||||
	statementRequest := StatementRequest{
 | 
			
		||||
		TrnUID: "123",
 | 
			
		||||
		BankAcctFrom: ofxgo.BankAcct{
 | 
			
		||||
		BankAcctFrom: BankAcct{
 | 
			
		||||
			BankID:   "318398732",
 | 
			
		||||
			AcctID:   "78346129",
 | 
			
		||||
			AcctType: ofxgo.AcctTypeChecking,
 | 
			
		||||
			AcctType: AcctTypeChecking,
 | 
			
		||||
		},
 | 
			
		||||
		Include: true,
 | 
			
		||||
	}
 | 
			
		||||
@@ -142,7 +141,7 @@ NEWFILEUID:NONE
 | 
			
		||||
	request.SetClientFields(&client)
 | 
			
		||||
	// Overwrite the DtClient value set by SetClientFields to time.Now()
 | 
			
		||||
	EST := time.FixedZone("EST", -5*60*60)
 | 
			
		||||
	request.Signon.DtClient = *ofxgo.NewDate(2006, 1, 15, 11, 23, 0, 0, EST)
 | 
			
		||||
	request.Signon.DtClient = *NewDate(2006, 1, 15, 11, 23, 0, 0, EST)
 | 
			
		||||
 | 
			
		||||
	marshalCheckRequest(t, &request, expectedString)
 | 
			
		||||
}
 | 
			
		||||
@@ -211,76 +210,77 @@ func TestUnmarshalBankStatementResponse(t *testing.T) {
 | 
			
		||||
		</STMTTRNRS>
 | 
			
		||||
	</BANKMSGSRSV1>
 | 
			
		||||
</OFX>`)
 | 
			
		||||
	var expected ofxgo.Response
 | 
			
		||||
	var expected Response
 | 
			
		||||
 | 
			
		||||
	expected.Version = ofxgo.OfxVersion203
 | 
			
		||||
	expected.Version = OfxVersion203
 | 
			
		||||
	expected.Signon.Status.Code = 0
 | 
			
		||||
	expected.Signon.Status.Severity = "INFO"
 | 
			
		||||
	expected.Signon.DtServer = *ofxgo.NewDateGMT(2006, 1, 15, 11, 23, 03, 0)
 | 
			
		||||
	expected.Signon.DtServer = *NewDateGMT(2006, 1, 15, 11, 23, 03, 0)
 | 
			
		||||
	expected.Signon.Language = "ENG"
 | 
			
		||||
	expected.Signon.DtProfUp = ofxgo.NewDateGMT(2005, 2, 21, 9, 13, 0, 0)
 | 
			
		||||
	expected.Signon.DtAcctUp = ofxgo.NewDateGMT(2006, 1, 2, 16, 0, 0, 0)
 | 
			
		||||
	expected.Signon.DtProfUp = NewDateGMT(2005, 2, 21, 9, 13, 0, 0)
 | 
			
		||||
	expected.Signon.DtAcctUp = NewDateGMT(2006, 1, 2, 16, 0, 0, 0)
 | 
			
		||||
	expected.Signon.Org = "BNK"
 | 
			
		||||
	expected.Signon.Fid = "1987"
 | 
			
		||||
 | 
			
		||||
	var trnamt1, trnamt2 ofxgo.Amount
 | 
			
		||||
	var trnamt1, trnamt2 Amount
 | 
			
		||||
	trnamt1.SetFrac64(-20000, 100)
 | 
			
		||||
	trnamt2.SetFrac64(-30000, 100)
 | 
			
		||||
 | 
			
		||||
	banktranlist := ofxgo.TransactionList{
 | 
			
		||||
		DtStart: *ofxgo.NewDateGMT(2006, 1, 1, 0, 0, 0, 0),
 | 
			
		||||
		DtEnd:   *ofxgo.NewDateGMT(2006, 1, 15, 0, 0, 0, 0),
 | 
			
		||||
		Transactions: []ofxgo.Transaction{
 | 
			
		||||
	banktranlist := TransactionList{
 | 
			
		||||
		DtStart: *NewDateGMT(2006, 1, 1, 0, 0, 0, 0),
 | 
			
		||||
		DtEnd:   *NewDateGMT(2006, 1, 15, 0, 0, 0, 0),
 | 
			
		||||
		Transactions: []Transaction{
 | 
			
		||||
			{
 | 
			
		||||
				TrnType:  ofxgo.TrnTypeCheck,
 | 
			
		||||
				DtPosted: *ofxgo.NewDateGMT(2006, 1, 4, 0, 0, 0, 0),
 | 
			
		||||
				TrnType:  TrnTypeCheck,
 | 
			
		||||
				DtPosted: *NewDateGMT(2006, 1, 4, 0, 0, 0, 0),
 | 
			
		||||
				TrnAmt:   trnamt1,
 | 
			
		||||
				FiTID:    "00592",
 | 
			
		||||
				CheckNum: "2002",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				TrnType:  ofxgo.TrnTypeATM,
 | 
			
		||||
				DtPosted: *ofxgo.NewDateGMT(2006, 1, 12, 0, 0, 0, 0),
 | 
			
		||||
				DtUser:   ofxgo.NewDateGMT(2006, 1, 12, 0, 0, 0, 0),
 | 
			
		||||
				TrnType:  TrnTypeATM,
 | 
			
		||||
				DtPosted: *NewDateGMT(2006, 1, 12, 0, 0, 0, 0),
 | 
			
		||||
				DtUser:   NewDateGMT(2006, 1, 12, 0, 0, 0, 0),
 | 
			
		||||
				TrnAmt:   trnamt2,
 | 
			
		||||
				FiTID:    "00679",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var balamt, availbalamt ofxgo.Amount
 | 
			
		||||
	var balamt, availbalamt Amount
 | 
			
		||||
	balamt.SetFrac64(20029, 100)
 | 
			
		||||
	availbalamt.SetFrac64(20029, 100)
 | 
			
		||||
 | 
			
		||||
	usd, err := ofxgo.NewCurrSymbol("USD")
 | 
			
		||||
	usd, err := NewCurrSymbol("USD")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating CurrSymbol for USD\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	statementResponse := ofxgo.StatementResponse{
 | 
			
		||||
	statementResponse := StatementResponse{
 | 
			
		||||
		TrnUID: "1001",
 | 
			
		||||
		Status: ofxgo.Status{
 | 
			
		||||
		Status: Status{
 | 
			
		||||
			Code:     0,
 | 
			
		||||
			Severity: "INFO",
 | 
			
		||||
		},
 | 
			
		||||
		CurDef: *usd,
 | 
			
		||||
		BankAcctFrom: ofxgo.BankAcct{
 | 
			
		||||
		BankAcctFrom: BankAcct{
 | 
			
		||||
			BankID:   "318398732",
 | 
			
		||||
			AcctID:   "78346129",
 | 
			
		||||
			AcctType: ofxgo.AcctTypeChecking,
 | 
			
		||||
			AcctType: AcctTypeChecking,
 | 
			
		||||
		},
 | 
			
		||||
		BankTranList: &banktranlist,
 | 
			
		||||
		BalAmt:       balamt,
 | 
			
		||||
		DtAsOf:       *ofxgo.NewDateGMT(2006, 1, 14, 16, 0, 0, 0),
 | 
			
		||||
		DtAsOf:       *NewDateGMT(2006, 1, 14, 16, 0, 0, 0),
 | 
			
		||||
		AvailBalAmt:  &availbalamt,
 | 
			
		||||
		AvailDtAsOf:  ofxgo.NewDateGMT(2006, 1, 14, 16, 0, 0, 0),
 | 
			
		||||
		AvailDtAsOf:  NewDateGMT(2006, 1, 14, 16, 0, 0, 0),
 | 
			
		||||
	}
 | 
			
		||||
	expected.Bank = append(expected.Bank, &statementResponse)
 | 
			
		||||
 | 
			
		||||
	response, err := ofxgo.ParseResponse(responseReader)
 | 
			
		||||
	response, err := ParseResponse(responseReader)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error unmarshalling response: %s\n", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	checkResponsesEqual(t, &expected, response)
 | 
			
		||||
	checkResponseRoundTrip(t, response)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										87
									
								
								basic_client.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								basic_client.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
package ofxgo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// BasicClient provides a standard Client implementation suitable for most
 | 
			
		||||
// financial institutions. BasicClient uses default, non-zero settings, even if
 | 
			
		||||
// its fields are not initialized.
 | 
			
		||||
type BasicClient struct {
 | 
			
		||||
	// Request fields to overwrite with the client's values. If nonempty,
 | 
			
		||||
	// defaults are used
 | 
			
		||||
	SpecVersion ofxVersion // VERSION in header
 | 
			
		||||
	AppID       string     // SONRQ>APPID
 | 
			
		||||
	AppVer      string     // SONRQ>APPVER
 | 
			
		||||
 | 
			
		||||
	// Don't insert newlines or indentation when marshalling to SGML/XML
 | 
			
		||||
	NoIndent bool
 | 
			
		||||
	// Use carriage returns on new lines
 | 
			
		||||
	CarriageReturn bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OfxVersion returns the OFX specification version this BasicClient will marshal
 | 
			
		||||
// Requests as. Defaults to "203" if the client's SpecVersion field is empty.
 | 
			
		||||
func (c *BasicClient) OfxVersion() ofxVersion {
 | 
			
		||||
	if c.SpecVersion.Valid() {
 | 
			
		||||
		return c.SpecVersion
 | 
			
		||||
	}
 | 
			
		||||
	return OfxVersion203
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ID returns this BasicClient's OFX AppID field, defaulting to "OFXGO" if
 | 
			
		||||
// unspecified.
 | 
			
		||||
func (c *BasicClient) ID() String {
 | 
			
		||||
	if len(c.AppID) > 0 {
 | 
			
		||||
		return String(c.AppID)
 | 
			
		||||
	}
 | 
			
		||||
	return String("OFXGO")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Version returns this BasicClient's version number as a string, defaulting to
 | 
			
		||||
// "0001" if unspecified.
 | 
			
		||||
func (c *BasicClient) Version() String {
 | 
			
		||||
	if len(c.AppVer) > 0 {
 | 
			
		||||
		return String(c.AppVer)
 | 
			
		||||
	}
 | 
			
		||||
	return String("0001")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IndentRequests returns true if the marshaled XML should be indented (and
 | 
			
		||||
// contain newlines, since the two are linked in the current implementation)
 | 
			
		||||
func (c *BasicClient) IndentRequests() bool {
 | 
			
		||||
	return !c.NoIndent
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CarriageReturnNewLines returns true if carriage returns should be used on new lines, false otherwise
 | 
			
		||||
func (c *BasicClient) CarriageReturnNewLines() bool {
 | 
			
		||||
	return c.CarriageReturn
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *BasicClient) RawRequest(URL string, r io.Reader) (*http.Response, error) {
 | 
			
		||||
	if !strings.HasPrefix(URL, "https://") {
 | 
			
		||||
		return nil, errors.New("Refusing to send OFX request with possible plain-text password over non-https protocol")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	response, err := http.Post(URL, "application/x-ofx", r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if response.StatusCode != 200 {
 | 
			
		||||
		return nil, errors.New("OFXQuery request status: " + response.Status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return response, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *BasicClient) RequestNoParse(r *Request) (*http.Response, error) {
 | 
			
		||||
	return clientRequestNoParse(c, r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *BasicClient) Request(r *Request) (*Response, error) {
 | 
			
		||||
	return clientRequest(c, r)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										202
									
								
								client.go
									
									
									
									
									
								
							
							
						
						
									
										202
									
								
								client.go
									
									
									
									
									
								
							@@ -1,127 +1,83 @@
 | 
			
		||||
package ofxgo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Client serves to aggregate OFX client settings that may be necessary to talk
 | 
			
		||||
// to a particular server due to quirks in that server's implementation. Client
 | 
			
		||||
// also provides the Request, RequestNoParse, and RawRequest helper methods to
 | 
			
		||||
// aid in making and parsing requests. Client uses default, non-zero settings,
 | 
			
		||||
// even if its fields are not initialized.
 | 
			
		||||
type Client struct {
 | 
			
		||||
	// Request fields to overwrite with the client's values. If nonempty,
 | 
			
		||||
	// defaults are used
 | 
			
		||||
	SpecVersion ofxVersion // VERSION in header
 | 
			
		||||
	AppID       string     // SONRQ>APPID
 | 
			
		||||
	AppVer      string     // SONRQ>APPVER
 | 
			
		||||
// to a particular server due to quirks in that server's implementation.
 | 
			
		||||
// Client also provides the Request and RequestNoParse helper methods to aid in
 | 
			
		||||
// making and parsing requests.
 | 
			
		||||
type Client interface {
 | 
			
		||||
	// Used to fill out a Request object
 | 
			
		||||
	OfxVersion() ofxVersion
 | 
			
		||||
	ID() String
 | 
			
		||||
	Version() String
 | 
			
		||||
	IndentRequests() bool
 | 
			
		||||
	CarriageReturnNewLines() bool
 | 
			
		||||
 | 
			
		||||
	// Don't insert newlines or indentation when marshalling to SGML/XML
 | 
			
		||||
	NoIndent bool
 | 
			
		||||
	// Request marshals a Request object into XML, makes an HTTP request
 | 
			
		||||
	// against it's URL, and then unmarshals the response into a Response
 | 
			
		||||
	// object.
 | 
			
		||||
	//
 | 
			
		||||
	// Before being marshaled, some of the the Request object's values are
 | 
			
		||||
	// overwritten, namely those dictated by the BasicClient's configuration
 | 
			
		||||
	// (Version, AppID, AppVer fields), and the client's current time
 | 
			
		||||
	// (DtClient). These are updated in place in the supplied Request object so
 | 
			
		||||
	// they may later be inspected by the caller.
 | 
			
		||||
	Request(r *Request) (*Response, error)
 | 
			
		||||
 | 
			
		||||
	// RequestNoParse marshals a Request object into XML, makes an HTTP
 | 
			
		||||
	// request, and returns the raw HTTP response. Unlike RawRequest(), it
 | 
			
		||||
	// takes client settings into account. Unlike Request(), it doesn't parse
 | 
			
		||||
	// the response into  an ofxgo.Request object.
 | 
			
		||||
	//
 | 
			
		||||
	// Caveat: The caller is responsible for closing the http Response.Body
 | 
			
		||||
	// (see the http module's documentation for more information)
 | 
			
		||||
	RequestNoParse(r *Request) (*http.Response, error)
 | 
			
		||||
 | 
			
		||||
	// RawRequest is little more than a thin wrapper around http.Post
 | 
			
		||||
	//
 | 
			
		||||
	// In most cases, you should probably be using Request() instead, but
 | 
			
		||||
	// RawRequest can be useful if you need to read the raw unparsed http
 | 
			
		||||
	// response yourself (perhaps for downloading an OFX file for use by an
 | 
			
		||||
	// external program, or debugging server behavior), or have a handcrafted
 | 
			
		||||
	// request you'd like to try.
 | 
			
		||||
	//
 | 
			
		||||
	// Caveats: RawRequest does *not* take client settings into account as
 | 
			
		||||
	// Client.Request() does, so your particular server may or may not like
 | 
			
		||||
	// whatever we read from 'r'. The caller is responsible for closing the
 | 
			
		||||
	// http Response.Body (see the http module's documentation for more
 | 
			
		||||
	// information)
 | 
			
		||||
	RawRequest(URL string, r io.Reader) (*http.Response, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var defaultClient Client
 | 
			
		||||
type clientCreationFunc func(*BasicClient) Client
 | 
			
		||||
 | 
			
		||||
// OfxVersion returns the OFX specification version this Client will marshal
 | 
			
		||||
// Requests as. Defaults to "203" if the client's SpecVersion field is empty.
 | 
			
		||||
func (c *Client) OfxVersion() ofxVersion {
 | 
			
		||||
	if c.SpecVersion.Valid() {
 | 
			
		||||
		return c.SpecVersion
 | 
			
		||||
// GetClient returns a new Client for a given URL. It attempts to find a
 | 
			
		||||
// specialized client for this URL, but simply returns the passed-in
 | 
			
		||||
// BasicClient if no such match is found.
 | 
			
		||||
func GetClient(URL string, bc *BasicClient) Client {
 | 
			
		||||
	clients := []struct {
 | 
			
		||||
		URL  string
 | 
			
		||||
		Func clientCreationFunc
 | 
			
		||||
	}{
 | 
			
		||||
		{"https://ofx.discovercard.com", NewDiscoverCardClient},
 | 
			
		||||
		{"https://vesnc.vanguard.com/us/OfxDirectConnectServlet", NewVanguardClient},
 | 
			
		||||
	}
 | 
			
		||||
	return OfxVersion203
 | 
			
		||||
	for _, client := range clients {
 | 
			
		||||
		if client.URL == strings.Trim(URL, "/") {
 | 
			
		||||
			return client.Func(bc)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return bc
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ID returns this Client's OFX AppID field, defaulting to "OFXGO" if
 | 
			
		||||
// unspecified.
 | 
			
		||||
func (c *Client) ID() String {
 | 
			
		||||
	if len(c.AppID) > 0 {
 | 
			
		||||
		return String(c.AppID)
 | 
			
		||||
	}
 | 
			
		||||
	return String("OFXGO")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Version returns this Client's version number as a string, defaulting to
 | 
			
		||||
// "0001" if unspecified.
 | 
			
		||||
func (c *Client) Version() String {
 | 
			
		||||
	if len(c.AppVer) > 0 {
 | 
			
		||||
		return String(c.AppVer)
 | 
			
		||||
	}
 | 
			
		||||
	return String("0001")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IndentRequests returns true if the marshaled XML should be indented (and
 | 
			
		||||
// contain newlines, since the two are linked in the current implementation)
 | 
			
		||||
func (c *Client) IndentRequests() bool {
 | 
			
		||||
	return !c.NoIndent
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RawRequest is little more than a thin wrapper around http.Post
 | 
			
		||||
//
 | 
			
		||||
// In most cases, you should probably be using Request() instead, but
 | 
			
		||||
// RawRequest can be useful if you need to read the raw unparsed http response
 | 
			
		||||
// yourself (perhaps for downloading an OFX file for use by an external
 | 
			
		||||
// program, or debugging server behavior), or have a handcrafted request you'd
 | 
			
		||||
// like to try.
 | 
			
		||||
//
 | 
			
		||||
// Caveats: RawRequest does *not* take client settings into account as
 | 
			
		||||
// Request() does, so your particular server may or may not like whatever we
 | 
			
		||||
// read from 'r'. The caller is responsible for closing the http Response.Body
 | 
			
		||||
// (see the http module's documentation for more information)
 | 
			
		||||
func RawRequest(URL string, r io.Reader) (*http.Response, error) {
 | 
			
		||||
	if !strings.HasPrefix(URL, "https://") {
 | 
			
		||||
		return nil, errors.New("Refusing to send OFX request with possible plain-text password over non-https protocol")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	response, err := http.Post(URL, "application/x-ofx", r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if response.StatusCode != 200 {
 | 
			
		||||
		return nil, errors.New("OFXQuery request status: " + response.Status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return response, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RawRequestCookies is RawRequest with the added feature of sending cookies
 | 
			
		||||
func RawRequestCookies(URL string, r io.Reader, cookies []*http.Cookie) (*http.Response, error) {
 | 
			
		||||
	if !strings.HasPrefix(URL, "https://") {
 | 
			
		||||
		return nil, errors.New("Refusing to send OFX request with possible plain-text password over non-https protocol")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	request, err := http.NewRequest("POST", URL, r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	request.Header.Set("Content-Type", "application/x-ofx")
 | 
			
		||||
	for _, cookie := range cookies {
 | 
			
		||||
		request.AddCookie(cookie)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	response, err := http.DefaultClient.Do(request)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if response.StatusCode != 200 {
 | 
			
		||||
		return nil, errors.New("OFXQuery request status: " + response.Status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return response, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RequestNoParse marshals a Request object into XML, makes an HTTP request,
 | 
			
		||||
// and returns the raw HTTP response. Unlike RawRequest(), it takes client
 | 
			
		||||
// settings into account. Unlike Request(), it doesn't parse the response into
 | 
			
		||||
// an ofxgo.Request object.
 | 
			
		||||
//
 | 
			
		||||
// Caveat: The caller is responsible for closing the http Response.Body (see
 | 
			
		||||
// the http module's documentation for more information)
 | 
			
		||||
func (c *Client) RequestNoParse(r *Request) (*http.Response, error) {
 | 
			
		||||
// clientRequestNoParse can be used for building clients' RequestNoParse
 | 
			
		||||
// methods if they require fairly standard behavior
 | 
			
		||||
func clientRequestNoParse(c Client, r *Request) (*http.Response, error) {
 | 
			
		||||
	r.SetClientFields(c)
 | 
			
		||||
 | 
			
		||||
	b, err := r.Marshal()
 | 
			
		||||
@@ -129,34 +85,12 @@ func (c *Client) RequestNoParse(r *Request) (*http.Response, error) {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	response, err := RawRequest(r.URL, b)
 | 
			
		||||
 | 
			
		||||
	// Some financial institutions (cough, Vanguard, cough), require a cookie
 | 
			
		||||
	// to be set on the http request, or they return empty responses.
 | 
			
		||||
	// Fortunately, the initial response contains the cookie we need, so if we
 | 
			
		||||
	// detect an empty response with cookies set that didn't have any errors,
 | 
			
		||||
	// re-try the request while sending their cookies back to them.
 | 
			
		||||
	if err == nil && response.ContentLength <= 0 && len(response.Cookies()) > 0 {
 | 
			
		||||
		b, err = r.Marshal()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return RawRequestCookies(r.URL, b, response.Cookies())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return response, err
 | 
			
		||||
	return c.RawRequest(r.URL, b)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Request marshals a Request object into XML, makes an HTTP request against
 | 
			
		||||
// it's URL, and then unmarshals the response into a Response object.
 | 
			
		||||
//
 | 
			
		||||
// Before being marshaled, some of the the Request object's values are
 | 
			
		||||
// overwritten, namely those dictated by the Client's configuration (Version,
 | 
			
		||||
// AppID, AppVer fields), and the client's curren time (DtClient). These are
 | 
			
		||||
// updated in place in the supplied Request object so they may later be
 | 
			
		||||
// inspected by the caller.
 | 
			
		||||
func (c *Client) Request(r *Request) (*Response, error) {
 | 
			
		||||
// clientRequest can be used for building clients' Request methods if they
 | 
			
		||||
// require fairly standard behavior
 | 
			
		||||
func clientRequest(c Client, r *Request) (*Response, error) {
 | 
			
		||||
	response, err := c.RequestNoParse(r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,8 @@ var appVersions = map[string][]string{
 | 
			
		||||
		"0001",
 | 
			
		||||
	},
 | 
			
		||||
	"QWIN": { // Intuit Quicken Windows
 | 
			
		||||
		"2600", // 2017
 | 
			
		||||
		"2500", // 2016
 | 
			
		||||
		"2400", // 2015
 | 
			
		||||
		"2300", // 2014
 | 
			
		||||
		"2200", // 2013
 | 
			
		||||
@@ -126,12 +128,13 @@ func tryProfile(appID, appVer, version string, noindent bool) bool {
 | 
			
		||||
		fmt.Println("Error creating new OfxVersion enum:", err)
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
	var client = ofxgo.Client{
 | 
			
		||||
	var client = ofxgo.GetClient(serverURL,
 | 
			
		||||
		&ofxgo.BasicClient{
 | 
			
		||||
			AppID:       appID,
 | 
			
		||||
			AppVer:      appVer,
 | 
			
		||||
			SpecVersion: ver,
 | 
			
		||||
			NoIndent:    noindent,
 | 
			
		||||
	}
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
	var query ofxgo.Request
 | 
			
		||||
	query.URL = serverURL
 | 
			
		||||
 
 | 
			
		||||
@@ -6,18 +6,19 @@ import (
 | 
			
		||||
	"os"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func newRequest() (*ofxgo.Client, *ofxgo.Request) {
 | 
			
		||||
func newRequest() (ofxgo.Client, *ofxgo.Request) {
 | 
			
		||||
	ver, err := ofxgo.NewOfxVersion(ofxVersion)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("Error creating new OfxVersion enum:", err)
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
	var client = ofxgo.Client{
 | 
			
		||||
	var client = ofxgo.GetClient(serverURL,
 | 
			
		||||
		&ofxgo.BasicClient{
 | 
			
		||||
			AppID:       appID,
 | 
			
		||||
			AppVer:      appVer,
 | 
			
		||||
			SpecVersion: ver,
 | 
			
		||||
			NoIndent:    noIndentRequests,
 | 
			
		||||
	}
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
	var query ofxgo.Request
 | 
			
		||||
	query.URL = serverURL
 | 
			
		||||
@@ -27,5 +28,5 @@ func newRequest() (*ofxgo.Client, *ofxgo.Request) {
 | 
			
		||||
	query.Signon.Org = ofxgo.String(org)
 | 
			
		||||
	query.Signon.Fid = ofxgo.String(fid)
 | 
			
		||||
 | 
			
		||||
	return &client, &query
 | 
			
		||||
	return client, &query
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										40
									
								
								common.go
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								common.go
									
									
									
									
									
								
							@@ -3,10 +3,50 @@ package ofxgo
 | 
			
		||||
//go:generate ./generate_constants.py
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/aclindsa/xml"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func writeHeader(b *bytes.Buffer, v ofxVersion, carriageReturn bool) error {
 | 
			
		||||
	// Write the header appropriate to our version
 | 
			
		||||
	switch v {
 | 
			
		||||
	case OfxVersion102, OfxVersion103, OfxVersion151, OfxVersion160:
 | 
			
		||||
		header := `OFXHEADER:100
 | 
			
		||||
DATA:OFXSGML
 | 
			
		||||
VERSION:` + v.String() + `
 | 
			
		||||
SECURITY:NONE
 | 
			
		||||
ENCODING:USASCII
 | 
			
		||||
CHARSET:1252
 | 
			
		||||
COMPRESSION:NONE
 | 
			
		||||
OLDFILEUID:NONE
 | 
			
		||||
NEWFILEUID:NONE
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
		if carriageReturn {
 | 
			
		||||
			header = strings.Replace(header, "\n", "\r\n", -1)
 | 
			
		||||
		}
 | 
			
		||||
		b.WriteString(header)
 | 
			
		||||
	case OfxVersion200, OfxVersion201, OfxVersion202, OfxVersion203, OfxVersion210, OfxVersion211, OfxVersion220:
 | 
			
		||||
		b.WriteString(`<?xml version="1.0" encoding="UTF-8" standalone="no"?>`)
 | 
			
		||||
		if carriageReturn {
 | 
			
		||||
			b.WriteByte('\r')
 | 
			
		||||
		}
 | 
			
		||||
		b.WriteByte('\n')
 | 
			
		||||
		b.WriteString(`<?OFX OFXHEADER="200" VERSION="` + v.String() + `" SECURITY="NONE" OLDFILEUID="NONE" NEWFILEUID="NONE"?>`)
 | 
			
		||||
		if carriageReturn {
 | 
			
		||||
			b.WriteByte('\r')
 | 
			
		||||
		}
 | 
			
		||||
		b.WriteByte('\n')
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("%d is not a valid OFX version string", v)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Message represents an OFX message in a message set. it is used to ease
 | 
			
		||||
// marshalling and unmarshalling.
 | 
			
		||||
type Message interface {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,11 @@
 | 
			
		||||
package ofxgo_test
 | 
			
		||||
package ofxgo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/aclindsa/ofxgo"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestStatusValid(t *testing.T) {
 | 
			
		||||
	s := ofxgo.Status{
 | 
			
		||||
	s := Status{
 | 
			
		||||
		Code:     0,
 | 
			
		||||
		Severity: "INFO",
 | 
			
		||||
		Message:  "Success",
 | 
			
		||||
@@ -32,7 +31,7 @@ func TestStatusValid(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStatusCodeMeaning(t *testing.T) {
 | 
			
		||||
	s := ofxgo.Status{
 | 
			
		||||
	s := Status{
 | 
			
		||||
		Code:     15500,
 | 
			
		||||
		Severity: "ERROR",
 | 
			
		||||
	}
 | 
			
		||||
@@ -51,7 +50,7 @@ func TestStatusCodeMeaning(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStatusCodeConditions(t *testing.T) {
 | 
			
		||||
	s := ofxgo.Status{
 | 
			
		||||
	s := Status{
 | 
			
		||||
		Code:     2006,
 | 
			
		||||
		Severity: "ERROR",
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,9 @@ package ofxgo
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/aclindsa/xml"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/aclindsa/xml"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ofxVersion uint
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package ofxgo_test
 | 
			
		||||
package ofxgo
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Do not edit this file by hand. It is auto-generated by calling `go generate`.
 | 
			
		||||
@@ -7,14 +7,14 @@ package ofxgo_test
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/aclindsa/xml"
 | 
			
		||||
	"github.com/aclindsa/ofxgo"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/aclindsa/xml"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestOfxVersion(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewOfxVersion("102")
 | 
			
		||||
	e, err := NewOfxVersion("102")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new OfxVersion from string \"102\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -31,7 +31,7 @@ func TestOfxVersion(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "220", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewOfxVersion("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewOfxVersion("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new OfxVersion from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -59,7 +59,7 @@ func TestOfxVersion(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAcctType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewAcctType("CHECKING")
 | 
			
		||||
	e, err := NewAcctType("CHECKING")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new AcctType from string \"CHECKING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -76,7 +76,7 @@ func TestAcctType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "CD", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewAcctType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewAcctType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new AcctType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -104,7 +104,7 @@ func TestAcctType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTrnType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewTrnType("CREDIT")
 | 
			
		||||
	e, err := NewTrnType("CREDIT")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new TrnType from string \"CREDIT\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -121,7 +121,7 @@ func TestTrnType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "OTHER", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewTrnType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewTrnType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new TrnType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -149,7 +149,7 @@ func TestTrnType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestImageType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewImageType("STATEMENT")
 | 
			
		||||
	e, err := NewImageType("STATEMENT")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new ImageType from string \"STATEMENT\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -166,7 +166,7 @@ func TestImageType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "TAX", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewImageType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewImageType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new ImageType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -194,7 +194,7 @@ func TestImageType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestImageRefType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewImageRefType("OPAQUE")
 | 
			
		||||
	e, err := NewImageRefType("OPAQUE")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new ImageRefType from string \"OPAQUE\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -211,7 +211,7 @@ func TestImageRefType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "FORMURL", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewImageRefType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewImageRefType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new ImageRefType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -239,7 +239,7 @@ func TestImageRefType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCheckSup(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewCheckSup("FRONTONLY")
 | 
			
		||||
	e, err := NewCheckSup("FRONTONLY")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new CheckSup from string \"FRONTONLY\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -256,7 +256,7 @@ func TestCheckSup(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "FRONTANDBACK", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewCheckSup("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewCheckSup("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new CheckSup from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -284,7 +284,7 @@ func TestCheckSup(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCorrectAction(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewCorrectAction("DELETE")
 | 
			
		||||
	e, err := NewCorrectAction("DELETE")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new CorrectAction from string \"DELETE\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -301,7 +301,7 @@ func TestCorrectAction(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "REPLACE", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewCorrectAction("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewCorrectAction("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new CorrectAction from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -329,7 +329,7 @@ func TestCorrectAction(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBalType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewBalType("DOLLAR")
 | 
			
		||||
	e, err := NewBalType("DOLLAR")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new BalType from string \"DOLLAR\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -346,7 +346,7 @@ func TestBalType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "NUMBER", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewBalType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewBalType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new BalType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -374,7 +374,7 @@ func TestBalType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestInv401kSource(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewInv401kSource("PRETAX")
 | 
			
		||||
	e, err := NewInv401kSource("PRETAX")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new Inv401kSource from string \"PRETAX\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -391,7 +391,7 @@ func TestInv401kSource(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "OTHERNONVEST", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewInv401kSource("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewInv401kSource("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new Inv401kSource from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -419,7 +419,7 @@ func TestInv401kSource(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSubAcctType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewSubAcctType("CASH")
 | 
			
		||||
	e, err := NewSubAcctType("CASH")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new SubAcctType from string \"CASH\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -436,7 +436,7 @@ func TestSubAcctType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "OTHER", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewSubAcctType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewSubAcctType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new SubAcctType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -464,7 +464,7 @@ func TestSubAcctType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBuyType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewBuyType("BUY")
 | 
			
		||||
	e, err := NewBuyType("BUY")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new BuyType from string \"BUY\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -481,7 +481,7 @@ func TestBuyType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "BUYTOCOVER", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewBuyType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewBuyType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new BuyType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -509,7 +509,7 @@ func TestBuyType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestOptAction(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewOptAction("EXERCISE")
 | 
			
		||||
	e, err := NewOptAction("EXERCISE")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new OptAction from string \"EXERCISE\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -526,7 +526,7 @@ func TestOptAction(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "EXPIRE", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewOptAction("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewOptAction("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new OptAction from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -554,7 +554,7 @@ func TestOptAction(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTferAction(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewTferAction("IN")
 | 
			
		||||
	e, err := NewTferAction("IN")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new TferAction from string \"IN\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -571,7 +571,7 @@ func TestTferAction(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "OUT", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewTferAction("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewTferAction("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new TferAction from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -599,7 +599,7 @@ func TestTferAction(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPosType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewPosType("LONG")
 | 
			
		||||
	e, err := NewPosType("LONG")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new PosType from string \"LONG\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -616,7 +616,7 @@ func TestPosType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "SHORT", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewPosType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewPosType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new PosType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -644,7 +644,7 @@ func TestPosType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSecured(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewSecured("NAKED")
 | 
			
		||||
	e, err := NewSecured("NAKED")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new Secured from string \"NAKED\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -661,7 +661,7 @@ func TestSecured(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "COVERED", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewSecured("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewSecured("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new Secured from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -689,7 +689,7 @@ func TestSecured(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDuration(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewDuration("DAY")
 | 
			
		||||
	e, err := NewDuration("DAY")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new Duration from string \"DAY\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -706,7 +706,7 @@ func TestDuration(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "IMMEDIATE", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewDuration("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewDuration("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new Duration from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -734,7 +734,7 @@ func TestDuration(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRestriction(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewRestriction("ALLORNONE")
 | 
			
		||||
	e, err := NewRestriction("ALLORNONE")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new Restriction from string \"ALLORNONE\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -751,7 +751,7 @@ func TestRestriction(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "NONE", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewRestriction("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewRestriction("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new Restriction from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -779,7 +779,7 @@ func TestRestriction(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUnitType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewUnitType("SHARES")
 | 
			
		||||
	e, err := NewUnitType("SHARES")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new UnitType from string \"SHARES\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -796,7 +796,7 @@ func TestUnitType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "CURRENCY", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewUnitType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewUnitType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new UnitType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -824,7 +824,7 @@ func TestUnitType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestOptBuyType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewOptBuyType("BUYTOOPEN")
 | 
			
		||||
	e, err := NewOptBuyType("BUYTOOPEN")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new OptBuyType from string \"BUYTOOPEN\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -841,7 +841,7 @@ func TestOptBuyType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "BUYTOCLOSE", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewOptBuyType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewOptBuyType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new OptBuyType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -869,7 +869,7 @@ func TestOptBuyType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSellType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewSellType("SELL")
 | 
			
		||||
	e, err := NewSellType("SELL")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new SellType from string \"SELL\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -886,7 +886,7 @@ func TestSellType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "SELLSHORT", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewSellType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewSellType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new SellType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -914,7 +914,7 @@ func TestSellType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoanPmtFreq(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewLoanPmtFreq("WEEKLY")
 | 
			
		||||
	e, err := NewLoanPmtFreq("WEEKLY")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new LoanPmtFreq from string \"WEEKLY\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -931,7 +931,7 @@ func TestLoanPmtFreq(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "OTHER", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewLoanPmtFreq("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewLoanPmtFreq("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new LoanPmtFreq from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -959,7 +959,7 @@ func TestLoanPmtFreq(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIncomeType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewIncomeType("CGLONG")
 | 
			
		||||
	e, err := NewIncomeType("CGLONG")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new IncomeType from string \"CGLONG\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -976,7 +976,7 @@ func TestIncomeType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "MISC", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewIncomeType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewIncomeType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new IncomeType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1004,7 +1004,7 @@ func TestIncomeType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSellReason(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewSellReason("CALL")
 | 
			
		||||
	e, err := NewSellReason("CALL")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new SellReason from string \"CALL\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1021,7 +1021,7 @@ func TestSellReason(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "MATURITY", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewSellReason("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewSellReason("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new SellReason from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1049,7 +1049,7 @@ func TestSellReason(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestOptSellType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewOptSellType("SELLTOCLOSE")
 | 
			
		||||
	e, err := NewOptSellType("SELLTOCLOSE")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new OptSellType from string \"SELLTOCLOSE\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1066,7 +1066,7 @@ func TestOptSellType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "SELLTOOPEN", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewOptSellType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewOptSellType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new OptSellType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1094,7 +1094,7 @@ func TestOptSellType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRelType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewRelType("SPREAD")
 | 
			
		||||
	e, err := NewRelType("SPREAD")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new RelType from string \"SPREAD\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1111,7 +1111,7 @@ func TestRelType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "OTHER", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewRelType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewRelType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new RelType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1139,7 +1139,7 @@ func TestRelType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCharType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewCharType("ALPHAONLY")
 | 
			
		||||
	e, err := NewCharType("ALPHAONLY")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new CharType from string \"ALPHAONLY\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1156,7 +1156,7 @@ func TestCharType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "ALPHAANDNUMERIC", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewCharType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewCharType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new CharType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1184,7 +1184,7 @@ func TestCharType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSyncMode(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewSyncMode("FULL")
 | 
			
		||||
	e, err := NewSyncMode("FULL")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new SyncMode from string \"FULL\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1201,7 +1201,7 @@ func TestSyncMode(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "LITE", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewSyncMode("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewSyncMode("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new SyncMode from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1229,7 +1229,7 @@ func TestSyncMode(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestOfxSec(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewOfxSec("NONE")
 | 
			
		||||
	e, err := NewOfxSec("NONE")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new OfxSec from string \"NONE\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1246,7 +1246,7 @@ func TestOfxSec(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "TYPE 1", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewOfxSec("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewOfxSec("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new OfxSec from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1274,7 +1274,7 @@ func TestOfxSec(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDebtType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewDebtType("COUPON")
 | 
			
		||||
	e, err := NewDebtType("COUPON")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new DebtType from string \"COUPON\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1291,7 +1291,7 @@ func TestDebtType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "ZERO", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewDebtType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewDebtType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new DebtType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1319,7 +1319,7 @@ func TestDebtType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDebtClass(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewDebtClass("TREASURY")
 | 
			
		||||
	e, err := NewDebtClass("TREASURY")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new DebtClass from string \"TREASURY\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1336,7 +1336,7 @@ func TestDebtClass(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "OTHER", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewDebtClass("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewDebtClass("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new DebtClass from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1364,7 +1364,7 @@ func TestDebtClass(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCouponFreq(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewCouponFreq("MONTHLY")
 | 
			
		||||
	e, err := NewCouponFreq("MONTHLY")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new CouponFreq from string \"MONTHLY\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1381,7 +1381,7 @@ func TestCouponFreq(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "OTHER", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewCouponFreq("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewCouponFreq("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new CouponFreq from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1409,7 +1409,7 @@ func TestCouponFreq(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCallType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewCallType("CALL")
 | 
			
		||||
	e, err := NewCallType("CALL")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new CallType from string \"CALL\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1426,7 +1426,7 @@ func TestCallType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "MATURITY", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewCallType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewCallType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new CallType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1454,7 +1454,7 @@ func TestCallType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAssetClass(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewAssetClass("DOMESTICBOND")
 | 
			
		||||
	e, err := NewAssetClass("DOMESTICBOND")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new AssetClass from string \"DOMESTICBOND\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1471,7 +1471,7 @@ func TestAssetClass(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "OTHER", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewAssetClass("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewAssetClass("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new AssetClass from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1499,7 +1499,7 @@ func TestAssetClass(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMfType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewMfType("OPENEND")
 | 
			
		||||
	e, err := NewMfType("OPENEND")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new MfType from string \"OPENEND\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1516,7 +1516,7 @@ func TestMfType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "OTHER", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewMfType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewMfType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new MfType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1544,7 +1544,7 @@ func TestMfType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestOptType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewOptType("PUT")
 | 
			
		||||
	e, err := NewOptType("PUT")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new OptType from string \"PUT\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1561,7 +1561,7 @@ func TestOptType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "CALL", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewOptType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewOptType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new OptType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1589,7 +1589,7 @@ func TestOptType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStockType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewStockType("COMMON")
 | 
			
		||||
	e, err := NewStockType("COMMON")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new StockType from string \"COMMON\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1606,7 +1606,7 @@ func TestStockType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "OTHER", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewStockType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewStockType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new StockType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1634,7 +1634,7 @@ func TestStockType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestHolderType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewHolderType("INDIVIDUAL")
 | 
			
		||||
	e, err := NewHolderType("INDIVIDUAL")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new HolderType from string \"INDIVIDUAL\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1651,7 +1651,7 @@ func TestHolderType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "OTHER", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewHolderType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewHolderType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new HolderType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1679,7 +1679,7 @@ func TestHolderType(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAcctClassification(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewAcctClassification("PERSONAL")
 | 
			
		||||
	e, err := NewAcctClassification("PERSONAL")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new AcctClassification from string \"PERSONAL\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1696,7 +1696,7 @@ func TestAcctClassification(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "OTHER", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewAcctClassification("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewAcctClassification("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new AcctClassification from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1724,7 +1724,7 @@ func TestAcctClassification(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSvcStatus(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewSvcStatus("AVAIL")
 | 
			
		||||
	e, err := NewSvcStatus("AVAIL")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new SvcStatus from string \"AVAIL\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1741,7 +1741,7 @@ func TestSvcStatus(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "ACTIVE", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewSvcStatus("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewSvcStatus("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new SvcStatus from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1769,7 +1769,7 @@ func TestSvcStatus(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUsProductType(t *testing.T) {
 | 
			
		||||
	e, err := ofxgo.NewUsProductType("401K")
 | 
			
		||||
	e, err := NewUsProductType("401K")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating new UsProductType from string \"401K\"\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1786,7 +1786,7 @@ func TestUsProductType(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "UGMA", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.NewUsProductType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := NewUsProductType("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("Expected error creating new UsProductType from string \"THISWILLNEVERBEAVALIDENUMSTRING\"\n")
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
package ofxgo_test
 | 
			
		||||
package ofxgo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/aclindsa/ofxgo"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -41,31 +40,31 @@ func TestMarshalCCStatementRequest(t *testing.T) {
 | 
			
		||||
	</CREDITCARDMSGSRQV1>
 | 
			
		||||
</OFX>`
 | 
			
		||||
 | 
			
		||||
	var client = ofxgo.Client{
 | 
			
		||||
	var client = BasicClient{
 | 
			
		||||
		AppID:       "OFXGO",
 | 
			
		||||
		AppVer:      "0001",
 | 
			
		||||
		SpecVersion: ofxgo.OfxVersion203,
 | 
			
		||||
		SpecVersion: OfxVersion203,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var request ofxgo.Request
 | 
			
		||||
	var request Request
 | 
			
		||||
	request.Signon.UserID = "myusername"
 | 
			
		||||
	request.Signon.UserPass = "Pa$$word"
 | 
			
		||||
	request.Signon.Org = "BNK"
 | 
			
		||||
	request.Signon.Fid = "1987"
 | 
			
		||||
 | 
			
		||||
	statementRequest := ofxgo.CCStatementRequest{
 | 
			
		||||
	statementRequest := CCStatementRequest{
 | 
			
		||||
		TrnUID: "913846",
 | 
			
		||||
		CCAcctFrom: ofxgo.CCAcct{
 | 
			
		||||
		CCAcctFrom: CCAcct{
 | 
			
		||||
			AcctID: "XXXXXXXXXXXX1234",
 | 
			
		||||
		},
 | 
			
		||||
		DtStart: ofxgo.NewDateGMT(2017, 1, 1, 0, 0, 0, 0),
 | 
			
		||||
		DtStart: NewDateGMT(2017, 1, 1, 0, 0, 0, 0),
 | 
			
		||||
		Include: true,
 | 
			
		||||
	}
 | 
			
		||||
	request.CreditCard = append(request.CreditCard, &statementRequest)
 | 
			
		||||
 | 
			
		||||
	request.SetClientFields(&client)
 | 
			
		||||
	// Overwrite the DtClient value set by SetClientFields to time.Now()
 | 
			
		||||
	request.Signon.DtClient = *ofxgo.NewDateGMT(2017, 3, 31, 15, 38, 48, 0)
 | 
			
		||||
	request.Signon.DtClient = *NewDateGMT(2017, 3, 31, 15, 38, 48, 0)
 | 
			
		||||
 | 
			
		||||
	marshalCheckRequest(t, &request, expectedString)
 | 
			
		||||
}
 | 
			
		||||
@@ -82,45 +81,45 @@ OLDFILEUID:NONE
 | 
			
		||||
NEWFILEUID:NONE
 | 
			
		||||
 | 
			
		||||
<OFX><SIGNONMSGSRSV1><SONRS><STATUS><CODE>0<SEVERITY>INFO<MESSAGE>SUCCESS</STATUS><DTSERVER>20170331154648.331[-4:EDT]<LANGUAGE>ENG<FI><ORG>01<FID>81729</FI></SONRS></SIGNONMSGSRSV1><CREDITCARDMSGSRSV1><CCSTMTTRNRS><TRNUID>59e850ad-7448-b4ce-4b71-29057763b306<STATUS><CODE>0<SEVERITY>INFO</STATUS><CCSTMTRS><CURDEF>USD<CCACCTFROM><ACCTID>9283744488463775</CCACCTFROM><BANKTRANLIST><DTSTART>20161201154648.688[-5:EST]<DTEND>20170331154648.688[-4:EDT]<STMTTRN><TRNTYPE>DEBIT<DTPOSTED>20170209120000[0:GMT]<TRNAMT>-7.96<FITID>2017020924435657040207171600195<NAME>SLICE OF NY</STMTTRN><STMTTRN><TRNTYPE>CREDIT<DTPOSTED>20161228120000[0:GMT]<TRNAMT>3830.46<FITID>2016122823633637200000258482730<NAME>Payment Thank You Electro</STMTTRN><STMTTRN><TRNTYPE>DEBIT<DTPOSTED>20170327120000[0:GMT]<TRNAMT>-17.7<FITID>2017032724445727085300442885680<NAME>KROGER FUEL #9999</STMTTRN></BANKTRANLIST><LEDGERBAL><BALAMT>-9334<DTASOF>20170331080000.000[-4:EDT]</LEDGERBAL><AVAILBAL><BALAMT>7630.17<DTASOF>20170331080000.000[-4:EDT]</AVAILBAL></CCSTMTRS></CCSTMTTRNRS></CREDITCARDMSGSRSV1></OFX>`)
 | 
			
		||||
	var expected ofxgo.Response
 | 
			
		||||
	var expected Response
 | 
			
		||||
	EDT := time.FixedZone("EDT", -4*60*60)
 | 
			
		||||
	EST := time.FixedZone("EST", -5*60*60)
 | 
			
		||||
 | 
			
		||||
	expected.Version = ofxgo.OfxVersion102
 | 
			
		||||
	expected.Version = OfxVersion102
 | 
			
		||||
	expected.Signon.Status.Code = 0
 | 
			
		||||
	expected.Signon.Status.Severity = "INFO"
 | 
			
		||||
	expected.Signon.Status.Message = "SUCCESS"
 | 
			
		||||
	expected.Signon.DtServer = *ofxgo.NewDate(2017, 3, 31, 15, 46, 48, 331000000, EDT)
 | 
			
		||||
	expected.Signon.DtServer = *NewDate(2017, 3, 31, 15, 46, 48, 331000000, EDT)
 | 
			
		||||
	expected.Signon.Language = "ENG"
 | 
			
		||||
	expected.Signon.Org = "01"
 | 
			
		||||
	expected.Signon.Fid = "81729"
 | 
			
		||||
 | 
			
		||||
	var trnamt1, trnamt2, trnamt3 ofxgo.Amount
 | 
			
		||||
	var trnamt1, trnamt2, trnamt3 Amount
 | 
			
		||||
	trnamt1.SetFrac64(-796, 100)
 | 
			
		||||
	trnamt2.SetFrac64(383046, 100)
 | 
			
		||||
	trnamt3.SetFrac64(-1770, 100)
 | 
			
		||||
 | 
			
		||||
	banktranlist := ofxgo.TransactionList{
 | 
			
		||||
		DtStart: *ofxgo.NewDate(2016, 12, 1, 15, 46, 48, 688000000, EST),
 | 
			
		||||
		DtEnd:   *ofxgo.NewDate(2017, 3, 31, 15, 46, 48, 688000000, EDT),
 | 
			
		||||
		Transactions: []ofxgo.Transaction{
 | 
			
		||||
	banktranlist := TransactionList{
 | 
			
		||||
		DtStart: *NewDate(2016, 12, 1, 15, 46, 48, 688000000, EST),
 | 
			
		||||
		DtEnd:   *NewDate(2017, 3, 31, 15, 46, 48, 688000000, EDT),
 | 
			
		||||
		Transactions: []Transaction{
 | 
			
		||||
			{
 | 
			
		||||
				TrnType:  ofxgo.TrnTypeDebit,
 | 
			
		||||
				DtPosted: *ofxgo.NewDateGMT(2017, 2, 9, 12, 0, 0, 0),
 | 
			
		||||
				TrnType:  TrnTypeDebit,
 | 
			
		||||
				DtPosted: *NewDateGMT(2017, 2, 9, 12, 0, 0, 0),
 | 
			
		||||
				TrnAmt:   trnamt1,
 | 
			
		||||
				FiTID:    "2017020924435657040207171600195",
 | 
			
		||||
				Name:     "SLICE OF NY",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				TrnType:  ofxgo.TrnTypeCredit,
 | 
			
		||||
				DtPosted: *ofxgo.NewDateGMT(2016, 12, 28, 12, 0, 0, 0),
 | 
			
		||||
				TrnType:  TrnTypeCredit,
 | 
			
		||||
				DtPosted: *NewDateGMT(2016, 12, 28, 12, 0, 0, 0),
 | 
			
		||||
				TrnAmt:   trnamt2,
 | 
			
		||||
				FiTID:    "2016122823633637200000258482730",
 | 
			
		||||
				Name:     "Payment Thank You Electro",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				TrnType:  ofxgo.TrnTypeDebit,
 | 
			
		||||
				DtPosted: *ofxgo.NewDateGMT(2017, 3, 27, 12, 0, 0, 0),
 | 
			
		||||
				TrnType:  TrnTypeDebit,
 | 
			
		||||
				DtPosted: *NewDateGMT(2017, 3, 27, 12, 0, 0, 0),
 | 
			
		||||
				TrnAmt:   trnamt3,
 | 
			
		||||
				FiTID:    "2017032724445727085300442885680",
 | 
			
		||||
				Name:     "KROGER FUEL #9999",
 | 
			
		||||
@@ -128,37 +127,38 @@ NEWFILEUID:NONE
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var balamt, availbalamt ofxgo.Amount
 | 
			
		||||
	var balamt, availbalamt Amount
 | 
			
		||||
	balamt.SetFrac64(-933400, 100)
 | 
			
		||||
	availbalamt.SetFrac64(763017, 100)
 | 
			
		||||
 | 
			
		||||
	usd, err := ofxgo.NewCurrSymbol("USD")
 | 
			
		||||
	usd, err := NewCurrSymbol("USD")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error creating CurrSymbol for USD\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	statementResponse := ofxgo.CCStatementResponse{
 | 
			
		||||
	statementResponse := CCStatementResponse{
 | 
			
		||||
		TrnUID: "59e850ad-7448-b4ce-4b71-29057763b306",
 | 
			
		||||
		Status: ofxgo.Status{
 | 
			
		||||
		Status: Status{
 | 
			
		||||
			Code:     0,
 | 
			
		||||
			Severity: "INFO",
 | 
			
		||||
		},
 | 
			
		||||
		CurDef: *usd,
 | 
			
		||||
		CCAcctFrom: ofxgo.CCAcct{
 | 
			
		||||
		CCAcctFrom: CCAcct{
 | 
			
		||||
			AcctID: "9283744488463775",
 | 
			
		||||
		},
 | 
			
		||||
		BankTranList: &banktranlist,
 | 
			
		||||
		BalAmt:       balamt,
 | 
			
		||||
		DtAsOf:       *ofxgo.NewDate(2017, 3, 31, 8, 0, 0, 0, EDT),
 | 
			
		||||
		DtAsOf:       *NewDate(2017, 3, 31, 8, 0, 0, 0, EDT),
 | 
			
		||||
		AvailBalAmt:  &availbalamt,
 | 
			
		||||
		AvailDtAsOf:  ofxgo.NewDate(2017, 3, 31, 8, 0, 0, 0, EDT),
 | 
			
		||||
		AvailDtAsOf:  NewDate(2017, 3, 31, 8, 0, 0, 0, EDT),
 | 
			
		||||
	}
 | 
			
		||||
	expected.CreditCard = append(expected.CreditCard, &statementResponse)
 | 
			
		||||
 | 
			
		||||
	response, err := ofxgo.ParseResponse(responseReader)
 | 
			
		||||
	response, err := ParseResponse(responseReader)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error unmarshalling response: %s\n", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	checkResponsesEqual(t, &expected, response)
 | 
			
		||||
	checkResponseRoundTrip(t, response)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										103
									
								
								discovercard_client.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								discovercard_client.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
package ofxgo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DiscoverCardClient provides a Client implementation which handles
 | 
			
		||||
// DiscoverCard's broken HTTP header behavior. DiscoverCardClient uses default,
 | 
			
		||||
// non-zero settings, if its fields are not initialized.
 | 
			
		||||
type DiscoverCardClient struct {
 | 
			
		||||
	*BasicClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewDiscoverCardClient returns a Client interface configured to handle
 | 
			
		||||
// Discover Card's brand of idiosyncracy
 | 
			
		||||
func NewDiscoverCardClient(bc *BasicClient) Client {
 | 
			
		||||
	return &DiscoverCardClient{bc}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func discoverCardHTTPPost(URL string, r io.Reader) (*http.Response, error) {
 | 
			
		||||
	// Either convert or copy to a bytes.Buffer to be able to determine the
 | 
			
		||||
	// request length for the Content-Length header
 | 
			
		||||
	buf, ok := r.(*bytes.Buffer)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		buf = &bytes.Buffer{}
 | 
			
		||||
		_, err := io.Copy(buf, r)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url, err := url.Parse(URL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	path := url.Path
 | 
			
		||||
	if path == "" {
 | 
			
		||||
		path = "/"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Discover requires only these headers and in this exact order, or it
 | 
			
		||||
	// returns HTTP 403
 | 
			
		||||
	headers := fmt.Sprintf("POST %s HTTP/1.1\r\n"+
 | 
			
		||||
		"Content-Type: application/x-ofx\r\n"+
 | 
			
		||||
		"Host: %s\r\n"+
 | 
			
		||||
		"Content-Length: %d\r\n"+
 | 
			
		||||
		"Connection: Keep-Alive\r\n"+
 | 
			
		||||
		"\r\n", path, url.Hostname(), buf.Len())
 | 
			
		||||
 | 
			
		||||
	host := url.Host
 | 
			
		||||
	if url.Port() == "" {
 | 
			
		||||
		host += ":443"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// BUGBUG: cannot do defer conn.Close() until body is read,
 | 
			
		||||
	// we are "leaking" a socket here, but it will be finalized
 | 
			
		||||
	conn, err := tls.Dial("tcp", host, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Fprint(conn, headers)
 | 
			
		||||
	_, err = io.Copy(conn, buf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return http.ReadResponse(bufio.NewReader(conn), nil)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *DiscoverCardClient) RawRequest(URL string, r io.Reader) (*http.Response, error) {
 | 
			
		||||
	if !strings.HasPrefix(URL, "https://") {
 | 
			
		||||
		return nil, errors.New("Refusing to send OFX request with possible plain-text password over non-https protocol")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	response, err := discoverCardHTTPPost(URL, r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if response.StatusCode != 200 {
 | 
			
		||||
		return nil, errors.New("OFXQuery request status: " + response.Status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return response, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *DiscoverCardClient) RequestNoParse(r *Request) (*http.Response, error) {
 | 
			
		||||
	return clientRequestNoParse(c, r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *DiscoverCardClient) Request(r *Request) (*Response, error) {
 | 
			
		||||
	return clientRequest(c, r)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								doc.go
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								doc.go
									
									
									
									
									
								
							@@ -71,33 +71,32 @@ account and print the balance:
 | 
			
		||||
 | 
			
		||||
  import (
 | 
			
		||||
    "fmt"
 | 
			
		||||
    "github.com/aclindsa/ofxgo"
 | 
			
		||||
    "os"
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  var client ofxgo.Client // By not initializing them, we accept all default
 | 
			
		||||
  var client Client // By not initializing them, we accept all default
 | 
			
		||||
                          // client values
 | 
			
		||||
  var request ofxgo.Request
 | 
			
		||||
  var request Request
 | 
			
		||||
 | 
			
		||||
  // These are all specific to you and your financial institution
 | 
			
		||||
  request.URL = "https://ofx.example.com"
 | 
			
		||||
  request.Signon.UserID = ofxgo.String("john")
 | 
			
		||||
  request.Signon.UserPass = ofxgo.String("hunter2")
 | 
			
		||||
  request.Signon.Org = ofxgo.String("MyBank")
 | 
			
		||||
  request.Signon.Fid = ofxgo.String("0001")
 | 
			
		||||
  request.Signon.UserID = String("john")
 | 
			
		||||
  request.Signon.UserPass = String("hunter2")
 | 
			
		||||
  request.Signon.Org = String("MyBank")
 | 
			
		||||
  request.Signon.Fid = String("0001")
 | 
			
		||||
 | 
			
		||||
  uid, err := ofxgo.RandomUID()
 | 
			
		||||
  uid, err := RandomUID()
 | 
			
		||||
  if err != nil {
 | 
			
		||||
    fmt.Println("Error creating uid for transaction:", err)
 | 
			
		||||
    os.Exit(1)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  statementRequest := ofxgo.StatementRequest{
 | 
			
		||||
  statementRequest := StatementRequest{
 | 
			
		||||
    TrnUID: *uid,
 | 
			
		||||
    BankAcctFrom: ofxgo.BankAcct{
 | 
			
		||||
      BankID:   ofxgo.String("123456789"),
 | 
			
		||||
      AcctID:   ofxgo.String("11111111111"),
 | 
			
		||||
      AcctType: ofxgo.AcctTypeChecking,
 | 
			
		||||
    BankAcctFrom: BankAcct{
 | 
			
		||||
      BankID:   String("123456789"),
 | 
			
		||||
      AcctID:   String("11111111111"),
 | 
			
		||||
      AcctType: AcctTypeChecking,
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -117,7 +116,7 @@ account and print the balance:
 | 
			
		||||
 | 
			
		||||
  if len(response.Bank) < 1 {
 | 
			
		||||
    fmt.Println("No banking messages received")
 | 
			
		||||
  } else if stmt, ok := response.Bank[0].(*ofxgo.StatementResponse); ok {
 | 
			
		||||
  } else if stmt, ok := response.Bank[0].(*StatementResponse); ok {
 | 
			
		||||
    fmt.Printf("Balance: %s %s (as of %s)\n", stmt.BalAmt, stmt.CurDef, stmt.DtAsOf)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -65,8 +65,9 @@ header = """package ofxgo
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/aclindsa/xml"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/aclindsa/xml"
 | 
			
		||||
)
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
@@ -164,7 +165,7 @@ with open("constants.go", 'w') as f:
 | 
			
		||||
                              constNames=constNames,
 | 
			
		||||
                              upperValueString=upperValueString))
 | 
			
		||||
 | 
			
		||||
test_header = """package ofxgo_test
 | 
			
		||||
test_header = """package ofxgo
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Do not edit this file by hand. It is auto-generated by calling `go generate`.
 | 
			
		||||
@@ -173,16 +174,16 @@ test_header = """package ofxgo_test
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/aclindsa/xml"
 | 
			
		||||
	"github.com/aclindsa/ofxgo"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/aclindsa/xml"
 | 
			
		||||
)
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
test_template = """
 | 
			
		||||
func Test{enum}(t *testing.T) {{
 | 
			
		||||
	e, err := ofxgo.New{enum}("{firstValueUpper}")
 | 
			
		||||
	e, err := New{enum}("{firstValueUpper}")
 | 
			
		||||
	if err != nil {{
 | 
			
		||||
		t.Fatalf("Unexpected error creating new {enum} from string \\\"{firstValueUpper}\\\"\\n")
 | 
			
		||||
	}}
 | 
			
		||||
@@ -199,7 +200,7 @@ func Test{enum}(t *testing.T) {{
 | 
			
		||||
 | 
			
		||||
	marshalHelper(t, "{lastValueUpper}", &e)
 | 
			
		||||
 | 
			
		||||
	overwritten, err := ofxgo.New{enum}("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	overwritten, err := New{enum}("THISWILLNEVERBEAVALIDENUMSTRING")
 | 
			
		||||
	if err == nil {{
 | 
			
		||||
		t.Fatalf("Expected error creating new {enum} from string \\\"THISWILLNEVERBEAVALIDENUMSTRING\\\"\\n")
 | 
			
		||||
	}}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
module github.com/aclindsa/ofxgo
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/aclindsa/xml v0.0.0-20190701095008-453d2c6090c2
 | 
			
		||||
	github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c
 | 
			
		||||
	golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4 // indirect
 | 
			
		||||
	golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 // indirect
 | 
			
		||||
	golang.org/x/text v0.0.0-20180911161511-905a57155faa
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
go 1.9
 | 
			
		||||
							
								
								
									
										14
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
github.com/aclindsa/xml v0.0.0-20171002130543-5d4402bb4a20 h1:wN3KlzWq56AIgOqFzYLYVih4zVyPDViCUeG5uZxJHq4=
 | 
			
		||||
github.com/aclindsa/xml v0.0.0-20171002130543-5d4402bb4a20/go.mod h1:DiEHtTD+e6zS3+R95F05Bfbcsfv13wZTi2M4LfAFLBE=
 | 
			
		||||
github.com/aclindsa/xml v0.0.0-20190625094425-0aa7a3409cf4 h1:STo5wlCItpgL9LFBui17kZ/N1iKQk+UztLRj2cVkSXQ=
 | 
			
		||||
github.com/aclindsa/xml v0.0.0-20190625094425-0aa7a3409cf4/go.mod h1:GjqOUT8xlg5+T19lFv6yAGNrtMKkZ839Gt4e16mBXlY=
 | 
			
		||||
github.com/aclindsa/xml v0.0.0-20190701095008-453d2c6090c2 h1:ICeGSGrc6fd81VtQ3nZ2h7GEOKxWYwRxjW0v0d/mgu4=
 | 
			
		||||
github.com/aclindsa/xml v0.0.0-20190701095008-453d2c6090c2/go.mod h1:GjqOUT8xlg5+T19lFv6yAGNrtMKkZ839Gt4e16mBXlY=
 | 
			
		||||
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c h1:kQWxfPIHVLbgLzphqk3QUflDy9QdksZR4ygR807bpy0=
 | 
			
		||||
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4 h1:Vk3wNqEZwyGyei9yq5ekj7frek2u7HUfffJ1/opblzc=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 h1:O33LKL7WyJgjN9CvxfTIomjIClbd/Kq86/iipowHQU0=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/text v0.0.0-20180911161511-905a57155faa h1:uIJ7KxPgS7ODNO//HqlPfjWmWDGRsoONAVcEVaJNWNs=
 | 
			
		||||
golang.org/x/text v0.0.0-20180911161511-905a57155faa/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
							
								
								
									
										216
									
								
								invstmt.go
									
									
									
									
									
								
							
							
						
						
									
										216
									
								
								invstmt.go
									
									
									
									
									
								
							@@ -465,6 +465,7 @@ type InvBankTransaction struct {
 | 
			
		||||
// security-related transactions themselves. It must be unmarshalled manually
 | 
			
		||||
// due to the structure (don't know what kind of InvTransaction is coming next)
 | 
			
		||||
type InvTranList struct {
 | 
			
		||||
	XMLName          xml.Name `xml:"INVTRANLIST"`
 | 
			
		||||
	DtStart          Date
 | 
			
		||||
	DtEnd            Date // This is the value that should be sent as <DTSTART> in the next InvStatementRequest to ensure that no transactions are missed
 | 
			
		||||
	InvTransactions  []InvTransaction
 | 
			
		||||
@@ -630,6 +631,119 @@ func (l *InvTranList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarshalXML handles marshalling an InvTranList element to an SGML/XML string
 | 
			
		||||
func (l *InvTranList) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
 | 
			
		||||
	invTranListElement := xml.StartElement{Name: xml.Name{Local: "INVTRANLIST"}}
 | 
			
		||||
	if err := e.EncodeToken(invTranListElement); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	err := e.EncodeElement(&l.DtStart, xml.StartElement{Name: xml.Name{Local: "DTSTART"}})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	err = e.EncodeElement(&l.DtEnd, xml.StartElement{Name: xml.Name{Local: "DTEND"}})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, t := range l.InvTransactions {
 | 
			
		||||
		start := xml.StartElement{Name: xml.Name{Local: t.TransactionType()}}
 | 
			
		||||
		switch tran := t.(type) {
 | 
			
		||||
		case BuyDebt:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case BuyMF:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case BuyOpt:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case BuyOther:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case BuyStock:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case ClosureOpt:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case Income:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case InvExpense:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case JrnlFund:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case JrnlSec:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case MarginInterest:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case Reinvest:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case RetOfCap:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case SellDebt:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case SellMF:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case SellOpt:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case SellOther:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case SellStock:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case Split:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case Transfer:
 | 
			
		||||
			if err := e.EncodeElement(&tran, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			return errors.New("Invalid INVTRANLIST child type: " + tran.TransactionType())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, tran := range l.BankTransactions {
 | 
			
		||||
		err = e.EncodeElement(&tran, xml.StartElement{Name: xml.Name{Local: "INVBANKTRAN"}})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := e.EncodeToken(invTranListElement.End()); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InvPosition contains generic position information included in each of the
 | 
			
		||||
// other *Position types
 | 
			
		||||
type InvPosition struct {
 | 
			
		||||
@@ -770,6 +884,45 @@ func (p *PositionList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) erro
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarshalXML handles marshalling a PositionList to an XML string
 | 
			
		||||
func (p *PositionList) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
 | 
			
		||||
	invPosListElement := xml.StartElement{Name: xml.Name{Local: "INVPOSLIST"}}
 | 
			
		||||
	if err := e.EncodeToken(invPosListElement); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, position := range *p {
 | 
			
		||||
		start := xml.StartElement{Name: xml.Name{Local: position.PositionType()}}
 | 
			
		||||
		switch pos := position.(type) {
 | 
			
		||||
		case DebtPosition:
 | 
			
		||||
			if err := e.EncodeElement(&pos, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case MFPosition:
 | 
			
		||||
			if err := e.EncodeElement(&pos, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case OptPosition:
 | 
			
		||||
			if err := e.EncodeElement(&pos, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case OtherPosition:
 | 
			
		||||
			if err := e.EncodeElement(&pos, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case StockPosition:
 | 
			
		||||
			if err := e.EncodeElement(&pos, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			return errors.New("Invalid INVPOSLIST child type: " + pos.PositionType())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := e.EncodeToken(invPosListElement.End()); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InvBalance contains three (or optionally four) specified balances as well as
 | 
			
		||||
// a free-form list of generic balance information which may be provided by an
 | 
			
		||||
// FI.
 | 
			
		||||
@@ -1036,6 +1189,69 @@ func (o *OOList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarshalXML handles marshalling an OOList to an XML string
 | 
			
		||||
func (o *OOList) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
 | 
			
		||||
	ooListElement := xml.StartElement{Name: xml.Name{Local: "INVOOLIST"}}
 | 
			
		||||
	if err := e.EncodeToken(ooListElement); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, openorder := range *o {
 | 
			
		||||
		start := xml.StartElement{Name: xml.Name{Local: openorder.OrderType()}}
 | 
			
		||||
		switch oo := openorder.(type) {
 | 
			
		||||
		case OOBuyDebt:
 | 
			
		||||
			if err := e.EncodeElement(&oo, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case OOBuyMF:
 | 
			
		||||
			if err := e.EncodeElement(&oo, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case OOBuyOpt:
 | 
			
		||||
			if err := e.EncodeElement(&oo, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case OOBuyOther:
 | 
			
		||||
			if err := e.EncodeElement(&oo, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case OOBuyStock:
 | 
			
		||||
			if err := e.EncodeElement(&oo, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case OOSellDebt:
 | 
			
		||||
			if err := e.EncodeElement(&oo, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case OOSellMF:
 | 
			
		||||
			if err := e.EncodeElement(&oo, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case OOSellOpt:
 | 
			
		||||
			if err := e.EncodeElement(&oo, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case OOSellOther:
 | 
			
		||||
			if err := e.EncodeElement(&oo, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case OOSellStock:
 | 
			
		||||
			if err := e.EncodeElement(&oo, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case OOSwitchMF:
 | 
			
		||||
			if err := e.EncodeElement(&oo, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			return errors.New("Invalid OOLIST child type: " + oo.OrderType())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := e.EncodeToken(ooListElement.End()); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ContribSecurity identifies current contribution allocation for a security in
 | 
			
		||||
// a 401(k) account
 | 
			
		||||
type ContribSecurity struct {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										743
									
								
								invstmt_test.go
									
									
									
									
									
								
							
							
						
						
									
										743
									
								
								invstmt_test.go
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -172,6 +172,7 @@ var ofxLeafElements = []string{
 | 
			
		||||
	"IDSCOPE",
 | 
			
		||||
	"INCBAL",
 | 
			
		||||
	"INCIMAGES",
 | 
			
		||||
	"INCLUDE",
 | 
			
		||||
	"INCOMETYPE",
 | 
			
		||||
	"INCOO",
 | 
			
		||||
	"INITIALAMT",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								profile.go
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								profile.go
									
									
									
									
									
								
							@@ -3,6 +3,7 @@ package ofxgo
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"github.com/aclindsa/xml"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ProfileRequest represents a request for a server to provide a profile of its
 | 
			
		||||
@@ -126,6 +127,35 @@ func (msl *MessageSetList) UnmarshalXML(d *xml.Decoder, start xml.StartElement)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarshalXML handles marshalling a MessageSetList element to an XML string
 | 
			
		||||
func (msl *MessageSetList) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
 | 
			
		||||
	messageSetListElement := xml.StartElement{Name: xml.Name{Local: "MSGSETLIST"}}
 | 
			
		||||
	if err := e.EncodeToken(messageSetListElement); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, messageset := range *msl {
 | 
			
		||||
		if !strings.HasSuffix(messageset.Name, "V1") {
 | 
			
		||||
			return errors.New("Expected MessageSet.Name to end with \"V1\"")
 | 
			
		||||
		}
 | 
			
		||||
		messageSetName := strings.TrimSuffix(messageset.Name, "V1")
 | 
			
		||||
		messageSetElement := xml.StartElement{Name: xml.Name{Local: messageSetName}}
 | 
			
		||||
		if err := e.EncodeToken(messageSetElement); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		start := xml.StartElement{Name: xml.Name{Local: messageset.Name}}
 | 
			
		||||
		if err := e.EncodeElement(&messageset, start); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := e.EncodeToken(messageSetElement.End()); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := e.EncodeToken(messageSetListElement.End()); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ProfileResponse contains a requested profile of the server's capabilities
 | 
			
		||||
// (which message sets and versions it supports, how to access them, which
 | 
			
		||||
// languages and which types of synchronization they support, etc.). Note that
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
package ofxgo_test
 | 
			
		||||
package ofxgo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/aclindsa/ofxgo"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -36,13 +35,13 @@ func TestMarshalProfileRequest(t *testing.T) {
 | 
			
		||||
	</PROFMSGSRQV1>
 | 
			
		||||
</OFX>`
 | 
			
		||||
 | 
			
		||||
	var client = ofxgo.Client{
 | 
			
		||||
	var client = BasicClient{
 | 
			
		||||
		AppID:       "OFXGO",
 | 
			
		||||
		AppVer:      "0001",
 | 
			
		||||
		SpecVersion: ofxgo.OfxVersion203,
 | 
			
		||||
		SpecVersion: OfxVersion203,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var request ofxgo.Request
 | 
			
		||||
	var request Request
 | 
			
		||||
	request.Signon.UserID = "anonymous00000000000000000000000"
 | 
			
		||||
	request.Signon.UserPass = "anonymous00000000000000000000000"
 | 
			
		||||
	request.Signon.Org = "BNK"
 | 
			
		||||
@@ -50,15 +49,15 @@ func TestMarshalProfileRequest(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	EST := time.FixedZone("EST", -5*60*60)
 | 
			
		||||
 | 
			
		||||
	profileRequest := ofxgo.ProfileRequest{
 | 
			
		||||
	profileRequest := ProfileRequest{
 | 
			
		||||
		TrnUID:   "983373",
 | 
			
		||||
		DtProfUp: *ofxgo.NewDate(2016, 1, 1, 0, 0, 0, 0, EST),
 | 
			
		||||
		DtProfUp: *NewDate(2016, 1, 1, 0, 0, 0, 0, EST),
 | 
			
		||||
	}
 | 
			
		||||
	request.Prof = append(request.Prof, &profileRequest)
 | 
			
		||||
 | 
			
		||||
	request.SetClientFields(&client)
 | 
			
		||||
	// Overwrite the DtClient value set by SetClientFields to time.Now()
 | 
			
		||||
	request.Signon.DtClient = *ofxgo.NewDate(2016, 6, 14, 7, 34, 0, 0, EST)
 | 
			
		||||
	request.Signon.DtClient = *NewDate(2016, 6, 14, 7, 34, 0, 0, EST)
 | 
			
		||||
 | 
			
		||||
	marshalCheckRequest(t, &request, expectedString)
 | 
			
		||||
}
 | 
			
		||||
@@ -213,89 +212,89 @@ NEWFILEUID:NONE
 | 
			
		||||
</PROFTRNRS>
 | 
			
		||||
</PROFMSGSRSV1>
 | 
			
		||||
</OFX>`)
 | 
			
		||||
	var expected ofxgo.Response
 | 
			
		||||
	var expected Response
 | 
			
		||||
 | 
			
		||||
	expected.Version = ofxgo.OfxVersion102
 | 
			
		||||
	expected.Version = OfxVersion102
 | 
			
		||||
	expected.Signon.Status.Code = 0
 | 
			
		||||
	expected.Signon.Status.Severity = "INFO"
 | 
			
		||||
	expected.Signon.DtServer = *ofxgo.NewDateGMT(2017, 4, 3, 9, 34, 58, 0)
 | 
			
		||||
	expected.Signon.DtServer = *NewDateGMT(2017, 4, 3, 9, 34, 58, 0)
 | 
			
		||||
	expected.Signon.Language = "ENG"
 | 
			
		||||
	expected.Signon.DtProfUp = ofxgo.NewDateGMT(2002, 11, 19, 14, 0, 0, 0)
 | 
			
		||||
	expected.Signon.DtProfUp = NewDateGMT(2002, 11, 19, 14, 0, 0, 0)
 | 
			
		||||
 | 
			
		||||
	profileResponse := ofxgo.ProfileResponse{
 | 
			
		||||
	profileResponse := ProfileResponse{
 | 
			
		||||
		TrnUID: "0f94ce83-13b7-7568-e4fc-c02c7b47e7ab",
 | 
			
		||||
		Status: ofxgo.Status{
 | 
			
		||||
		Status: Status{
 | 
			
		||||
			Code:     0,
 | 
			
		||||
			Severity: "INFO",
 | 
			
		||||
		},
 | 
			
		||||
		MessageSetList: ofxgo.MessageSetList{
 | 
			
		||||
			ofxgo.MessageSet{
 | 
			
		||||
		MessageSetList: MessageSetList{
 | 
			
		||||
			MessageSet{
 | 
			
		||||
				Name:        "SIGNONMSGSETV1",
 | 
			
		||||
				Ver:         1,
 | 
			
		||||
				URL:         "https://ofx.example.com/cgi-ofx/exampleofx",
 | 
			
		||||
				OfxSec:      ofxgo.OfxSecNone,
 | 
			
		||||
				OfxSec:      OfxSecNone,
 | 
			
		||||
				TranspSec:   true,
 | 
			
		||||
				SignonRealm: "Example Trade",
 | 
			
		||||
				Language:    []ofxgo.String{"ENG"},
 | 
			
		||||
				SyncMode:    ofxgo.SyncModeLite,
 | 
			
		||||
				Language:    []String{"ENG"},
 | 
			
		||||
				SyncMode:    SyncModeLite,
 | 
			
		||||
				RespFileER:  false,
 | 
			
		||||
				// Ignored: <INTU.TIMEOUT>300
 | 
			
		||||
			},
 | 
			
		||||
			ofxgo.MessageSet{
 | 
			
		||||
			MessageSet{
 | 
			
		||||
				Name:        "SIGNUPMSGSETV1",
 | 
			
		||||
				Ver:         1,
 | 
			
		||||
				URL:         "https://ofx.example.com/cgi-ofx/exampleofx",
 | 
			
		||||
				OfxSec:      ofxgo.OfxSecNone,
 | 
			
		||||
				OfxSec:      OfxSecNone,
 | 
			
		||||
				TranspSec:   true,
 | 
			
		||||
				SignonRealm: "Example Trade",
 | 
			
		||||
				Language:    []ofxgo.String{"ENG"},
 | 
			
		||||
				SyncMode:    ofxgo.SyncModeLite,
 | 
			
		||||
				Language:    []String{"ENG"},
 | 
			
		||||
				SyncMode:    SyncModeLite,
 | 
			
		||||
				RespFileER:  false,
 | 
			
		||||
				// Ignored: <INTU.TIMEOUT>300
 | 
			
		||||
			},
 | 
			
		||||
			ofxgo.MessageSet{
 | 
			
		||||
			MessageSet{
 | 
			
		||||
				Name:        "INVSTMTMSGSETV1",
 | 
			
		||||
				Ver:         1,
 | 
			
		||||
				URL:         "https://ofx.example.com/cgi-ofx/exampleofx",
 | 
			
		||||
				OfxSec:      ofxgo.OfxSecNone,
 | 
			
		||||
				OfxSec:      OfxSecNone,
 | 
			
		||||
				TranspSec:   true,
 | 
			
		||||
				SignonRealm: "Example Trade",
 | 
			
		||||
				Language:    []ofxgo.String{"ENG"},
 | 
			
		||||
				SyncMode:    ofxgo.SyncModeLite,
 | 
			
		||||
				Language:    []String{"ENG"},
 | 
			
		||||
				SyncMode:    SyncModeLite,
 | 
			
		||||
				RespFileER:  false,
 | 
			
		||||
				// Ignored: <INTU.TIMEOUT>300
 | 
			
		||||
			},
 | 
			
		||||
			ofxgo.MessageSet{
 | 
			
		||||
			MessageSet{
 | 
			
		||||
				Name:        "SECLISTMSGSETV1",
 | 
			
		||||
				Ver:         1,
 | 
			
		||||
				URL:         "https://ofx.example.com/cgi-ofx/exampleofx",
 | 
			
		||||
				OfxSec:      ofxgo.OfxSecNone,
 | 
			
		||||
				OfxSec:      OfxSecNone,
 | 
			
		||||
				TranspSec:   true,
 | 
			
		||||
				SignonRealm: "Example Trade",
 | 
			
		||||
				Language:    []ofxgo.String{"ENG"},
 | 
			
		||||
				SyncMode:    ofxgo.SyncModeLite,
 | 
			
		||||
				Language:    []String{"ENG"},
 | 
			
		||||
				SyncMode:    SyncModeLite,
 | 
			
		||||
				RespFileER:  false,
 | 
			
		||||
				// Ignored: <INTU.TIMEOUT>300
 | 
			
		||||
			},
 | 
			
		||||
			ofxgo.MessageSet{
 | 
			
		||||
			MessageSet{
 | 
			
		||||
				Name:        "PROFMSGSETV1",
 | 
			
		||||
				Ver:         1,
 | 
			
		||||
				URL:         "https://ofx.example.com/cgi-ofx/exampleofx",
 | 
			
		||||
				OfxSec:      ofxgo.OfxSecNone,
 | 
			
		||||
				OfxSec:      OfxSecNone,
 | 
			
		||||
				TranspSec:   true,
 | 
			
		||||
				SignonRealm: "Example Trade",
 | 
			
		||||
				Language:    []ofxgo.String{"ENG"},
 | 
			
		||||
				SyncMode:    ofxgo.SyncModeLite,
 | 
			
		||||
				Language:    []String{"ENG"},
 | 
			
		||||
				SyncMode:    SyncModeLite,
 | 
			
		||||
				RespFileER:  false,
 | 
			
		||||
				// Ignored: <INTU.TIMEOUT>300
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		SignonInfoList: []ofxgo.SignonInfo{
 | 
			
		||||
		SignonInfoList: []SignonInfo{
 | 
			
		||||
			{
 | 
			
		||||
				SignonRealm: "Example Trade",
 | 
			
		||||
				Min:         1,
 | 
			
		||||
				Max:         32,
 | 
			
		||||
				CharType:    ofxgo.CharTypeAlphaOrNumeric,
 | 
			
		||||
				CharType:    CharTypeAlphaOrNumeric,
 | 
			
		||||
				CaseSen:     false,
 | 
			
		||||
				Special:     true,
 | 
			
		||||
				Spaces:      false,
 | 
			
		||||
@@ -303,7 +302,7 @@ NEWFILEUID:NONE
 | 
			
		||||
				ChgPinFirst: false,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		DtProfUp:   *ofxgo.NewDateGMT(2002, 11, 19, 14, 0, 0, 0),
 | 
			
		||||
		DtProfUp:   *NewDateGMT(2002, 11, 19, 14, 0, 0, 0),
 | 
			
		||||
		FiName:     "Example Trade Financial",
 | 
			
		||||
		Addr1:      "5555 Buhunkus Drive",
 | 
			
		||||
		City:       "Someville",
 | 
			
		||||
@@ -319,10 +318,11 @@ NEWFILEUID:NONE
 | 
			
		||||
	}
 | 
			
		||||
	expected.Prof = append(expected.Prof, &profileResponse)
 | 
			
		||||
 | 
			
		||||
	response, err := ofxgo.ParseResponse(responseReader)
 | 
			
		||||
	response, err := ParseResponse(responseReader)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error unmarshalling response: %s\n", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	checkResponsesEqual(t, &expected, response)
 | 
			
		||||
	checkResponseRoundTrip(t, response)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								request.go
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								request.go
									
									
									
									
									
								
							@@ -3,7 +3,6 @@ package ofxgo
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/aclindsa/xml"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
@@ -33,9 +32,10 @@ type Request struct {
 | 
			
		||||
	Image      []Message     //<IMAGEMSGSETV1>
 | 
			
		||||
 | 
			
		||||
	indent         bool // Whether to indent the marshaled XML
 | 
			
		||||
	carriageReturn bool // Whether to user carriage returns in new lines for marshaled XML
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func marshalMessageSet(e *xml.Encoder, requests []Message, set messageType, version ofxVersion) error {
 | 
			
		||||
func encodeMessageSet(e *xml.Encoder, requests []Message, set messageType, version ofxVersion) error {
 | 
			
		||||
	if len(requests) > 0 {
 | 
			
		||||
		messageSetElement := xml.StartElement{Name: xml.Name{Local: set.String()}}
 | 
			
		||||
		if err := e.EncodeToken(messageSetElement); err != nil {
 | 
			
		||||
@@ -63,7 +63,7 @@ func marshalMessageSet(e *xml.Encoder, requests []Message, set messageType, vers
 | 
			
		||||
 | 
			
		||||
// SetClientFields overwrites the fields in this Request object controlled by
 | 
			
		||||
// the Client
 | 
			
		||||
func (oq *Request) SetClientFields(c *Client) {
 | 
			
		||||
func (oq *Request) SetClientFields(c Client) {
 | 
			
		||||
	oq.Signon.DtClient.Time = time.Now()
 | 
			
		||||
 | 
			
		||||
	// Overwrite fields that the client controls
 | 
			
		||||
@@ -71,6 +71,7 @@ func (oq *Request) SetClientFields(c *Client) {
 | 
			
		||||
	oq.Signon.AppID = c.ID()
 | 
			
		||||
	oq.Signon.AppVer = c.Version()
 | 
			
		||||
	oq.indent = c.IndentRequests()
 | 
			
		||||
	oq.carriageReturn = c.CarriageReturnNewLines()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Marshal this Request into its SGML/XML representation held in a bytes.Buffer
 | 
			
		||||
@@ -80,30 +81,19 @@ func (oq *Request) Marshal() (*bytes.Buffer, error) {
 | 
			
		||||
	var b bytes.Buffer
 | 
			
		||||
 | 
			
		||||
	// Write the header appropriate to our version
 | 
			
		||||
	switch oq.Version {
 | 
			
		||||
	case OfxVersion102, OfxVersion103, OfxVersion151, OfxVersion160:
 | 
			
		||||
		b.WriteString(`OFXHEADER:100
 | 
			
		||||
DATA:OFXSGML
 | 
			
		||||
VERSION:` + oq.Version.String() + `
 | 
			
		||||
SECURITY:NONE
 | 
			
		||||
ENCODING:USASCII
 | 
			
		||||
CHARSET:1252
 | 
			
		||||
COMPRESSION:NONE
 | 
			
		||||
OLDFILEUID:NONE
 | 
			
		||||
NEWFILEUID:NONE
 | 
			
		||||
 | 
			
		||||
`)
 | 
			
		||||
	case OfxVersion200, OfxVersion201, OfxVersion202, OfxVersion203, OfxVersion210, OfxVersion211, OfxVersion220:
 | 
			
		||||
		b.WriteString(`<?xml version="1.0" encoding="UTF-8" standalone="no"?>` + "\n")
 | 
			
		||||
		b.WriteString(`<?OFX OFXHEADER="200" VERSION="` + oq.Version.String() + `" SECURITY="NONE" OLDFILEUID="NONE" NEWFILEUID="NONE"?>` + "\n")
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, fmt.Errorf("%d is not a valid OFX version string", oq.Version)
 | 
			
		||||
	}
 | 
			
		||||
	writeHeader(&b, oq.Version, oq.carriageReturn)
 | 
			
		||||
 | 
			
		||||
	encoder := xml.NewEncoder(&b)
 | 
			
		||||
	if oq.indent {
 | 
			
		||||
		encoder.Indent("", "    ")
 | 
			
		||||
	}
 | 
			
		||||
	if oq.carriageReturn {
 | 
			
		||||
		encoder.CarriageReturn(true)
 | 
			
		||||
	}
 | 
			
		||||
	if oq.Version < OfxVersion200 {
 | 
			
		||||
		// OFX 100 series versions should avoid element close tags for compatibility
 | 
			
		||||
		encoder.SetDisableAutoClose(ofxLeafElements...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ofxElement := xml.StartElement{Name: xml.Name{Local: "OFX"}}
 | 
			
		||||
 | 
			
		||||
@@ -145,7 +135,7 @@ NEWFILEUID:NONE
 | 
			
		||||
		{oq.Image, ImageRq},
 | 
			
		||||
	}
 | 
			
		||||
	for _, set := range messageSets {
 | 
			
		||||
		if err := marshalMessageSet(encoder, set.Messages, set.Type, oq.Version); err != nil {
 | 
			
		||||
		if err := encodeMessageSet(encoder, set.Messages, set.Type, oq.Version); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,16 @@
 | 
			
		||||
package ofxgo_test
 | 
			
		||||
package ofxgo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/aclindsa/ofxgo"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var ignoreSpacesRe = regexp.MustCompile(">[ \t\r\n]+<")
 | 
			
		||||
// match leading and trailing whitespace on each line
 | 
			
		||||
var ignoreSpacesRe = regexp.MustCompile("(?m)^[ \t]+|[ \t]*$[\r\n]+")
 | 
			
		||||
 | 
			
		||||
func marshalCheckRequest(t *testing.T, request *ofxgo.Request, expected string) {
 | 
			
		||||
func marshalCheckRequest(t *testing.T, request *Request, expected string) {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
	buf, err := request.Marshal()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("%s: Unexpected error marshalling request: %s\n", t.Name(), err)
 | 
			
		||||
@@ -17,8 +18,8 @@ func marshalCheckRequest(t *testing.T, request *ofxgo.Request, expected string)
 | 
			
		||||
	actualString := buf.String()
 | 
			
		||||
 | 
			
		||||
	// Ignore spaces between XML elements
 | 
			
		||||
	expectedString := ignoreSpacesRe.ReplaceAllString(expected, "><")
 | 
			
		||||
	actualString = ignoreSpacesRe.ReplaceAllString(actualString, "><")
 | 
			
		||||
	expectedString := ignoreSpacesRe.ReplaceAllString(expected, "")
 | 
			
		||||
	actualString = ignoreSpacesRe.ReplaceAllString(actualString, "")
 | 
			
		||||
 | 
			
		||||
	if expectedString != actualString {
 | 
			
		||||
		compareLength := len(expectedString)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										164
									
								
								response.go
									
									
									
									
									
								
							
							
						
						
									
										164
									
								
								response.go
									
									
									
									
									
								
							@@ -4,10 +4,12 @@ import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"github.com/aclindsa/xml"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/aclindsa/xml"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Response is the top-level object returned from a parsed OFX response file.
 | 
			
		||||
@@ -35,6 +37,16 @@ type Response struct {
 | 
			
		||||
func (or *Response) readSGMLHeaders(r *bufio.Reader) error {
 | 
			
		||||
	var seenHeader, seenVersion bool = false, false
 | 
			
		||||
	for {
 | 
			
		||||
		// Some financial institutions do not properly leave an empty line after the last header.
 | 
			
		||||
		// Avoid attempting to read another header in that case.
 | 
			
		||||
		next, err := r.Peek(1)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if next[0] == '<' {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		line, err := r.ReadString('\n')
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
@@ -175,6 +187,7 @@ const guessVersionCheckBytes = 1024
 | 
			
		||||
 | 
			
		||||
// Defaults to XML if it can't determine the version or if there is any
 | 
			
		||||
// ambiguity
 | 
			
		||||
// Returns false for SGML, true (for XML) otherwise.
 | 
			
		||||
func guessVersion(r *bufio.Reader) (bool, error) {
 | 
			
		||||
	b, _ := r.Peek(guessVersionCheckBytes)
 | 
			
		||||
	if b == nil {
 | 
			
		||||
@@ -245,9 +258,6 @@ func decodeMessageSet(d *xml.Decoder, start xml.StartElement, msgs *[]Message, v
 | 
			
		||||
			if err := d.DecodeElement(responseMessage, &startElement); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			if ok, err := responseMessage.Valid(version); !ok {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			*msgs = append(*msgs, responseMessage)
 | 
			
		||||
		} else {
 | 
			
		||||
			return errors.New("Didn't find an opening element")
 | 
			
		||||
@@ -255,14 +265,25 @@ func decodeMessageSet(d *xml.Decoder, start xml.StartElement, msgs *[]Message, v
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseResponse parses an OFX response in SGML or XML into a Response object
 | 
			
		||||
// from the given io.Reader
 | 
			
		||||
// ParseResponse parses and validates an OFX response in SGML or XML into a
 | 
			
		||||
// Response object from the given io.Reader
 | 
			
		||||
//
 | 
			
		||||
// It is commonly used as part of Client.Request(), but may be used on its own
 | 
			
		||||
// to parse already-downloaded OFX files (such as those from 'Web Connect'). It
 | 
			
		||||
// performs version autodetection if it can and attempts to be as forgiving as
 | 
			
		||||
// possible about the input format.
 | 
			
		||||
func ParseResponse(reader io.Reader) (*Response, error) {
 | 
			
		||||
	resp, err := DecodeResponse(reader)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	_, err = resp.Valid()
 | 
			
		||||
	return resp, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DecodeResponse parses an OFX response in SGML or XML into a Response object
 | 
			
		||||
// from the given io.Reader
 | 
			
		||||
func DecodeResponse(reader io.Reader) (*Response, error) {
 | 
			
		||||
	var or Response
 | 
			
		||||
 | 
			
		||||
	r := bufio.NewReaderSize(reader, guessVersionCheckBytes)
 | 
			
		||||
@@ -319,9 +340,6 @@ func ParseResponse(reader io.Reader) (*Response, error) {
 | 
			
		||||
	} else if signonEnd, ok := tok.(xml.EndElement); !ok || signonEnd.Name.Local != SignonRs.String() {
 | 
			
		||||
		return nil, errors.New("Missing closing SIGNONMSGSRSV1 xml element")
 | 
			
		||||
	}
 | 
			
		||||
	if ok, err := or.Signon.Valid(or.Version); !ok {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var messageSlices = map[string]*[]Message{
 | 
			
		||||
		SignupRs.String():     &or.Signup,
 | 
			
		||||
@@ -359,3 +377,131 @@ func ParseResponse(reader io.Reader) (*Response, error) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Valid returns whether the Response is valid according to the OFX spec
 | 
			
		||||
func (or *Response) Valid() (bool, error) {
 | 
			
		||||
	var errs errInvalid
 | 
			
		||||
	if ok, err := or.Signon.Valid(or.Version); !ok {
 | 
			
		||||
		errs.AddErr(err)
 | 
			
		||||
	}
 | 
			
		||||
	for _, messageSet := range [][]Message{
 | 
			
		||||
		or.Signup,
 | 
			
		||||
		or.Bank,
 | 
			
		||||
		or.CreditCard,
 | 
			
		||||
		or.Loan,
 | 
			
		||||
		or.InvStmt,
 | 
			
		||||
		or.InterXfer,
 | 
			
		||||
		or.WireXfer,
 | 
			
		||||
		or.Billpay,
 | 
			
		||||
		or.Email,
 | 
			
		||||
		or.SecList,
 | 
			
		||||
		or.PresDir,
 | 
			
		||||
		or.PresDlv,
 | 
			
		||||
		or.Prof,
 | 
			
		||||
		or.Image,
 | 
			
		||||
	} {
 | 
			
		||||
		for _, message := range messageSet {
 | 
			
		||||
			if ok, err := message.Valid(or.Version); !ok {
 | 
			
		||||
				errs.AddErr(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	err := errs.ErrOrNil()
 | 
			
		||||
	return err == nil, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Marshal this Response into its SGML/XML representation held in a bytes.Buffer
 | 
			
		||||
//
 | 
			
		||||
// If error is non-nil, this bytes.Buffer is ready to be sent to an OFX client
 | 
			
		||||
func (or *Response) Marshal() (*bytes.Buffer, error) {
 | 
			
		||||
	var b bytes.Buffer
 | 
			
		||||
 | 
			
		||||
	// Write the header appropriate to our version
 | 
			
		||||
	writeHeader(&b, or.Version, false)
 | 
			
		||||
 | 
			
		||||
	encoder := xml.NewEncoder(&b)
 | 
			
		||||
	encoder.Indent("", "    ")
 | 
			
		||||
 | 
			
		||||
	ofxElement := xml.StartElement{Name: xml.Name{Local: "OFX"}}
 | 
			
		||||
 | 
			
		||||
	if err := encoder.EncodeToken(ofxElement); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ok, err := or.Signon.Valid(or.Version); !ok {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	signonMsgSet := xml.StartElement{Name: xml.Name{Local: SignonRs.String()}}
 | 
			
		||||
	if err := encoder.EncodeToken(signonMsgSet); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if err := encoder.Encode(&or.Signon); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if err := encoder.EncodeToken(signonMsgSet.End()); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	messageSets := []struct {
 | 
			
		||||
		Messages []Message
 | 
			
		||||
		Type     messageType
 | 
			
		||||
	}{
 | 
			
		||||
		{or.Signup, SignupRs},
 | 
			
		||||
		{or.Bank, BankRs},
 | 
			
		||||
		{or.CreditCard, CreditCardRs},
 | 
			
		||||
		{or.Loan, LoanRs},
 | 
			
		||||
		{or.InvStmt, InvStmtRs},
 | 
			
		||||
		{or.InterXfer, InterXferRs},
 | 
			
		||||
		{or.WireXfer, WireXferRs},
 | 
			
		||||
		{or.Billpay, BillpayRs},
 | 
			
		||||
		{or.Email, EmailRs},
 | 
			
		||||
		{or.SecList, SecListRs},
 | 
			
		||||
		{or.PresDir, PresDirRs},
 | 
			
		||||
		{or.PresDlv, PresDlvRs},
 | 
			
		||||
		{or.Prof, ProfRs},
 | 
			
		||||
		{or.Image, ImageRs},
 | 
			
		||||
	}
 | 
			
		||||
	for _, set := range messageSets {
 | 
			
		||||
		if err := encodeMessageSet(encoder, set.Messages, set.Type, or.Version); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := encoder.EncodeToken(ofxElement.End()); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := encoder.Flush(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &b, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// errInvalid represents validation failures while parsing an OFX response
 | 
			
		||||
// If an institution returns slightly malformed data, ParseResponse will return a best-effort parsed response and a validation error.
 | 
			
		||||
type errInvalid []error
 | 
			
		||||
 | 
			
		||||
func (e errInvalid) Error() string {
 | 
			
		||||
	var errStrings []string
 | 
			
		||||
	for _, err := range e {
 | 
			
		||||
		errStrings = append(errStrings, err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("Validation failed: %s", strings.Join(errStrings, "; "))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *errInvalid) AddErr(err error) {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if errs, ok := err.(errInvalid); ok {
 | 
			
		||||
			*e = append(*e, errs...)
 | 
			
		||||
		} else {
 | 
			
		||||
			*e = append(*e, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e errInvalid) ErrOrNil() error {
 | 
			
		||||
	if len(e) > 0 {
 | 
			
		||||
		return e
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										180
									
								
								response_test.go
									
									
									
									
									
								
							
							
						
						
									
										180
									
								
								response_test.go
									
									
									
									
									
								
							@@ -1,13 +1,15 @@
 | 
			
		||||
package ofxgo_test
 | 
			
		||||
package ofxgo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/aclindsa/xml"
 | 
			
		||||
	"github.com/aclindsa/ofxgo"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/aclindsa/xml"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Attempt to find a method on the provided Value called 'Equal' which is a
 | 
			
		||||
@@ -132,27 +134,191 @@ func checkEqual(t *testing.T, fieldName string, expected, actual reflect.Value)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkResponsesEqual(t *testing.T, expected, actual *ofxgo.Response) {
 | 
			
		||||
func checkResponsesEqual(t *testing.T, expected, actual *Response) {
 | 
			
		||||
	checkEqual(t, "", reflect.ValueOf(expected), reflect.ValueOf(actual))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkResponseRoundTrip(t *testing.T, response *Response) {
 | 
			
		||||
	b, err := response.Marshal()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error re-marshaling OFX response: %s\n", err)
 | 
			
		||||
	}
 | 
			
		||||
	roundtripped, err := ParseResponse(b)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error re-parsing OFX response: %s\n", err)
 | 
			
		||||
	}
 | 
			
		||||
	checkResponsesEqual(t, response, roundtripped)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Ensure that these samples both parse without errors, and can be converted
 | 
			
		||||
// back and forth without changing.
 | 
			
		||||
func TestValidSamples(t *testing.T) {
 | 
			
		||||
	fn := func(path string, info os.FileInfo, err error) error {
 | 
			
		||||
		if info.IsDir() {
 | 
			
		||||
			return nil
 | 
			
		||||
		} else if filepath.Ext(path) != ".ofx" {
 | 
			
		||||
		} else if ext := filepath.Ext(path); ext != ".ofx" && ext != ".qfx" {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		file, err := os.Open(path)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatalf("Unexpected error opening %s: %s\n", path, err)
 | 
			
		||||
		}
 | 
			
		||||
		_, err = ofxgo.ParseResponse(file)
 | 
			
		||||
		response, err := ParseResponse(file)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatalf("Unexpected error parsing OFX response in %s: %s\n", path, err)
 | 
			
		||||
		}
 | 
			
		||||
		checkResponseRoundTrip(t, response)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	filepath.Walk("samples/valid_responses", fn)
 | 
			
		||||
	filepath.Walk("samples/busted_responses", fn)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestInvalidResponse(t *testing.T) {
 | 
			
		||||
	// in this example, the severity is invalid due to mixed upper and lower case letters
 | 
			
		||||
	const invalidResponse = `
 | 
			
		||||
OFXHEADER:100
 | 
			
		||||
DATA:OFXSGML
 | 
			
		||||
VERSION:102
 | 
			
		||||
SECURITY:NONE
 | 
			
		||||
ENCODING:USASCII
 | 
			
		||||
CHARSET:1252
 | 
			
		||||
COMPRESSION:NONE
 | 
			
		||||
OLDFILEUID:NONE
 | 
			
		||||
NEWFILEUID:NONE
 | 
			
		||||
 | 
			
		||||
<OFX>
 | 
			
		||||
	<SIGNONMSGSRSV1>
 | 
			
		||||
		<SONRS>
 | 
			
		||||
			<STATUS>
 | 
			
		||||
				<CODE>0</CODE>
 | 
			
		||||
				<SEVERITY>Info</SEVERITY>
 | 
			
		||||
			</STATUS>
 | 
			
		||||
			<LANGUAGE>ENG</LANGUAGE>
 | 
			
		||||
		</SONRS>
 | 
			
		||||
	</SIGNONMSGSRSV1>
 | 
			
		||||
	<BANKMSGSRSV1>
 | 
			
		||||
		<STMTTRNRS>
 | 
			
		||||
			<TRNUID>0</TRNUID>
 | 
			
		||||
			<STATUS>
 | 
			
		||||
				<CODE>0</CODE>
 | 
			
		||||
				<SEVERITY>Info</SEVERITY>
 | 
			
		||||
			</STATUS>
 | 
			
		||||
		</STMTTRNRS>
 | 
			
		||||
	</BANKMSGSRSV1>
 | 
			
		||||
</OFX>
 | 
			
		||||
`
 | 
			
		||||
	const expectedErr = "Validation failed: Invalid STATUS>SEVERITY; Invalid STATUS>SEVERITY"
 | 
			
		||||
 | 
			
		||||
	t.Run("parse response", func(t *testing.T) {
 | 
			
		||||
		resp, err := ParseResponse(bytes.NewReader([]byte(invalidResponse)))
 | 
			
		||||
		expectedErr := "Validation failed: Invalid STATUS>SEVERITY; Invalid STATUS>SEVERITY"
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			t.Fatalf("ParseResponse should fail with %q, found nil", expectedErr)
 | 
			
		||||
		}
 | 
			
		||||
		if _, ok := err.(errInvalid); !ok {
 | 
			
		||||
			t.Errorf("ParseResponse should return an error with type ErrInvalid, found %T", err)
 | 
			
		||||
		}
 | 
			
		||||
		if err.Error() != expectedErr {
 | 
			
		||||
			t.Errorf("ParseResponse should fail with %q, found %v", expectedErr, err)
 | 
			
		||||
		}
 | 
			
		||||
		if resp == nil {
 | 
			
		||||
			t.Errorf("Response must not be nil if only validation errors are present")
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("parse failed", func(t *testing.T) {
 | 
			
		||||
		resp, err := ParseResponse(bytes.NewReader(nil))
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			t.Error("ParseResponse should fail to decode")
 | 
			
		||||
		}
 | 
			
		||||
		if resp != nil {
 | 
			
		||||
			t.Errorf("ParseResponse should return a nil response, found: %v", resp)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("decode, then validate response", func(t *testing.T) {
 | 
			
		||||
		resp, err := DecodeResponse(bytes.NewReader([]byte(invalidResponse)))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Unexpected error: %s", err.Error())
 | 
			
		||||
		}
 | 
			
		||||
		if resp == nil {
 | 
			
		||||
			t.Fatal("Response should not be nil from successful decode")
 | 
			
		||||
		}
 | 
			
		||||
		valid, err := resp.Valid()
 | 
			
		||||
		if valid {
 | 
			
		||||
			t.Error("Response should not be valid")
 | 
			
		||||
		}
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			t.Fatalf("response.Valid() should fail with %q, found nil", expectedErr)
 | 
			
		||||
		}
 | 
			
		||||
		if _, ok := err.(errInvalid); !ok {
 | 
			
		||||
			t.Errorf("response.Valid() should return an error of type ErrInvalid, found: %T", err)
 | 
			
		||||
		}
 | 
			
		||||
		if err.Error() != expectedErr {
 | 
			
		||||
			t.Errorf("response.Valid() should return an error with message %q, but found %q", expectedErr, err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestErrInvalidError(t *testing.T) {
 | 
			
		||||
	expectedErr := `Validation failed: A; B; C`
 | 
			
		||||
	actualErr := errInvalid{
 | 
			
		||||
		errors.New("A"),
 | 
			
		||||
		errors.New("B"),
 | 
			
		||||
		errors.New("C"),
 | 
			
		||||
	}.Error()
 | 
			
		||||
	if expectedErr != actualErr {
 | 
			
		||||
		t.Errorf("Unexpected invalid error message to be %q, but was: %s", expectedErr, actualErr)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestErrInvalidAddErr(t *testing.T) {
 | 
			
		||||
	t.Run("nil error should be a no-op", func(t *testing.T) {
 | 
			
		||||
		var errs errInvalid
 | 
			
		||||
		errs.AddErr(nil)
 | 
			
		||||
		if len(errs) != 0 {
 | 
			
		||||
			t.Errorf("Nil err should not be added")
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("adds an error normally", func(t *testing.T) {
 | 
			
		||||
		var errs errInvalid
 | 
			
		||||
		errs.AddErr(errors.New("some error"))
 | 
			
		||||
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("adding the same type should flatten the errors", func(t *testing.T) {
 | 
			
		||||
		var errs errInvalid
 | 
			
		||||
		errs.AddErr(errInvalid{
 | 
			
		||||
			errors.New("A"),
 | 
			
		||||
			errors.New("B"),
 | 
			
		||||
		})
 | 
			
		||||
		errs.AddErr(errInvalid{
 | 
			
		||||
			errors.New("C"),
 | 
			
		||||
		})
 | 
			
		||||
		if len(errs) != 3 {
 | 
			
		||||
			t.Errorf("Errors should be flattened like [A, B, C], but found: %+v", errs)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestErrInvalidErrOrNil(t *testing.T) {
 | 
			
		||||
	var errs errInvalid
 | 
			
		||||
	if err := errs.ErrOrNil(); err != nil {
 | 
			
		||||
		t.Errorf("No added errors should return nil, found: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	someError := errors.New("some error")
 | 
			
		||||
	errs.AddErr(someError)
 | 
			
		||||
	err := errs.ErrOrNil()
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatal("Expected an error, found nil.")
 | 
			
		||||
	}
 | 
			
		||||
	if _, ok := err.(errInvalid); !ok {
 | 
			
		||||
		t.Fatalf("Expected err to be of type errInvalid, found: %T", err)
 | 
			
		||||
	}
 | 
			
		||||
	errInv := err.(errInvalid)
 | 
			
		||||
	if len(errInv) != 1 || errInv[0] != someError {
 | 
			
		||||
		t.Errorf("Expected ErrOrNil to return itself, found: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										59
									
								
								samples/busted_responses/bmo_v102__no_header_newline.qfx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								samples/busted_responses/bmo_v102__no_header_newline.qfx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
OFXHEADER:100
 | 
			
		||||
DATA:OFXSGML
 | 
			
		||||
VERSION:102
 | 
			
		||||
SECURITY:NONE
 | 
			
		||||
ENCODING:USASCII
 | 
			
		||||
CHARSET:1252
 | 
			
		||||
COMPRESSION:NONE
 | 
			
		||||
OLDFILEUID:NONE
 | 
			
		||||
NEWFILEUID:NONE
 | 
			
		||||
<OFX>
 | 
			
		||||
<SIGNONMSGSRSV1>
 | 
			
		||||
<SONRS>
 | 
			
		||||
<STATUS>
 | 
			
		||||
<CODE>0
 | 
			
		||||
<SEVERITY>INFO
 | 
			
		||||
<MESSAGE>OK
 | 
			
		||||
</STATUS>
 | 
			
		||||
<DTSERVER>20181202184906.217[-5:EDT]
 | 
			
		||||
<USERKEY>SJLDF802DV09DF80
 | 
			
		||||
<LANGUAGE>ENG
 | 
			
		||||
<INTU.BID>00017
 | 
			
		||||
</SONRS>
 | 
			
		||||
</SIGNONMSGSRSV1>
 | 
			
		||||
<CREDITCARDMSGSRSV1>
 | 
			
		||||
<CCSTMTTRNRS>
 | 
			
		||||
<TRNUID>1
 | 
			
		||||
<STATUS>
 | 
			
		||||
<CODE>0
 | 
			
		||||
<SEVERITY>INFO
 | 
			
		||||
<MESSAGE>OK
 | 
			
		||||
</STATUS>
 | 
			
		||||
<CCSTMTRS>
 | 
			
		||||
<CURDEF>CAD
 | 
			
		||||
<CCACCTFROM>
 | 
			
		||||
<ACCTID>2380370270281083
 | 
			
		||||
</CCACCTFROM>
 | 
			
		||||
<BANKTRANLIST>
 | 
			
		||||
<DTSTART>20181202184905.909[-5:EDT]
 | 
			
		||||
<DTEND>20181202184905.909[-5:EDT]
 | 
			
		||||
<STMTTRN>
 | 
			
		||||
<TRNTYPE>CREDIT
 | 
			
		||||
<DTPOSTED>20181030000000.000[-5:EDT]
 | 
			
		||||
<TRNAMT>2042.24
 | 
			
		||||
<FITID>2380370270281083201810302054456
 | 
			
		||||
<NAME>PAYMENT RECEIVED - THANK YOU
 | 
			
		||||
</STMTTRN>
 | 
			
		||||
</BANKTRANLIST>
 | 
			
		||||
<LEDGERBAL>
 | 
			
		||||
<BALAMT>-552.63
 | 
			
		||||
<DTASOF>20181202184906.217[-5:EDT]
 | 
			
		||||
</LEDGERBAL>
 | 
			
		||||
<AVAILBAL>
 | 
			
		||||
<BALAMT>-552.63
 | 
			
		||||
<DTASOF>20181202184906.217[-5:EDT]
 | 
			
		||||
</AVAILBAL>
 | 
			
		||||
</CCSTMTRS>
 | 
			
		||||
</CCSTMTTRNRS>
 | 
			
		||||
</CREDITCARDMSGSRSV1>
 | 
			
		||||
</OFX>
 | 
			
		||||
							
								
								
									
										40
									
								
								seclist.go
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								seclist.go
									
									
									
									
									
								
							@@ -221,6 +221,7 @@ func (i StockInfo) SecurityType() string {
 | 
			
		||||
// SecurityList is a container for Security objects containaing information
 | 
			
		||||
// about securities
 | 
			
		||||
type SecurityList struct {
 | 
			
		||||
	XMLName    xml.Name `xml:"SECLIST"`
 | 
			
		||||
	Securities []Security
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -290,3 +291,42 @@ func (r *SecurityList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) erro
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarshalXML handles marshalling a SecurityList to an SGML/XML string
 | 
			
		||||
func (r *SecurityList) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
 | 
			
		||||
	secListElement := xml.StartElement{Name: xml.Name{Local: "SECLIST"}}
 | 
			
		||||
	if err := e.EncodeToken(secListElement); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, s := range r.Securities {
 | 
			
		||||
		start := xml.StartElement{Name: xml.Name{Local: s.SecurityType()}}
 | 
			
		||||
		switch sec := s.(type) {
 | 
			
		||||
		case DebtInfo:
 | 
			
		||||
			if err := e.EncodeElement(&sec, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case MFInfo:
 | 
			
		||||
			if err := e.EncodeElement(&sec, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case OptInfo:
 | 
			
		||||
			if err := e.EncodeElement(&sec, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case OtherInfo:
 | 
			
		||||
			if err := e.EncodeElement(&sec, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		case StockInfo:
 | 
			
		||||
			if err := e.EncodeElement(&sec, start); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			return errors.New("Invalid SECLIST child type: " + sec.SecurityType())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := e.EncodeToken(secListElement.End()); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,17 @@
 | 
			
		||||
package ofxgo_test
 | 
			
		||||
package ofxgo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/aclindsa/ofxgo"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestMarshalInvalidSignons(t *testing.T) {
 | 
			
		||||
	var client = ofxgo.Client{
 | 
			
		||||
	var client = BasicClient{
 | 
			
		||||
		AppID:       "OFXGO",
 | 
			
		||||
		AppVer:      "0001",
 | 
			
		||||
		SpecVersion: ofxgo.OfxVersion203,
 | 
			
		||||
		SpecVersion: OfxVersion203,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var request ofxgo.Request
 | 
			
		||||
	var request Request
 | 
			
		||||
	request.Signon.UserID = "myusername"
 | 
			
		||||
	request.Signon.UserPass = "Pa$$word"
 | 
			
		||||
	request.Signon.Org = "BNK"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
package ofxgo_test
 | 
			
		||||
package ofxgo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/aclindsa/ofxgo"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -37,27 +36,27 @@ func TestMarshalAcctInfoRequest(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	EST := time.FixedZone("EST", -5*60*60)
 | 
			
		||||
 | 
			
		||||
	var client = ofxgo.Client{
 | 
			
		||||
	var client = BasicClient{
 | 
			
		||||
		AppID:       "OFXGO",
 | 
			
		||||
		AppVer:      "0001",
 | 
			
		||||
		SpecVersion: ofxgo.OfxVersion203,
 | 
			
		||||
		SpecVersion: OfxVersion203,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var request ofxgo.Request
 | 
			
		||||
	var request Request
 | 
			
		||||
	request.Signon.UserID = "myusername"
 | 
			
		||||
	request.Signon.UserPass = "Pa$$word"
 | 
			
		||||
	request.Signon.Org = "BNK"
 | 
			
		||||
	request.Signon.Fid = "1987"
 | 
			
		||||
 | 
			
		||||
	acctInfoRequest := ofxgo.AcctInfoRequest{
 | 
			
		||||
	acctInfoRequest := AcctInfoRequest{
 | 
			
		||||
		TrnUID:   "e3ad9bda-38fa-4e5b-8099-1bd567ddef7a",
 | 
			
		||||
		DtAcctUp: *ofxgo.NewDate(2015, 12, 21, 18, 29, 45, 0, EST),
 | 
			
		||||
		DtAcctUp: *NewDate(2015, 12, 21, 18, 29, 45, 0, EST),
 | 
			
		||||
	}
 | 
			
		||||
	request.Signup = append(request.Signup, &acctInfoRequest)
 | 
			
		||||
 | 
			
		||||
	request.SetClientFields(&client)
 | 
			
		||||
	// Overwrite the DtClient value set by SetClientFields to time.Now()
 | 
			
		||||
	request.Signon.DtClient = *ofxgo.NewDate(2016, 1, 15, 11, 23, 0, 0, EST)
 | 
			
		||||
	request.Signon.DtClient = *NewDate(2016, 1, 15, 11, 23, 0, 0, EST)
 | 
			
		||||
 | 
			
		||||
	marshalCheckRequest(t, &request, expectedString)
 | 
			
		||||
}
 | 
			
		||||
@@ -110,38 +109,38 @@ func TestUnmarshalAcctInfoResponse(t *testing.T) {
 | 
			
		||||
		</ACCTINFOTRNRS>
 | 
			
		||||
	</SIGNUPMSGSRSV1>
 | 
			
		||||
</OFX>`)
 | 
			
		||||
	var expected ofxgo.Response
 | 
			
		||||
	var expected Response
 | 
			
		||||
 | 
			
		||||
	expected.Version = ofxgo.OfxVersion203
 | 
			
		||||
	expected.Version = OfxVersion203
 | 
			
		||||
	expected.Signon.Status.Code = 0
 | 
			
		||||
	expected.Signon.Status.Severity = "INFO"
 | 
			
		||||
	expected.Signon.DtServer = *ofxgo.NewDateGMT(2006, 1, 15, 11, 23, 03, 0)
 | 
			
		||||
	expected.Signon.DtServer = *NewDateGMT(2006, 1, 15, 11, 23, 03, 0)
 | 
			
		||||
	expected.Signon.Language = "ENG"
 | 
			
		||||
	expected.Signon.DtProfUp = ofxgo.NewDateGMT(2005, 2, 21, 9, 13, 0, 0)
 | 
			
		||||
	expected.Signon.DtAcctUp = ofxgo.NewDateGMT(2006, 1, 2, 16, 0, 0, 0)
 | 
			
		||||
	expected.Signon.DtProfUp = NewDateGMT(2005, 2, 21, 9, 13, 0, 0)
 | 
			
		||||
	expected.Signon.DtAcctUp = NewDateGMT(2006, 1, 2, 16, 0, 0, 0)
 | 
			
		||||
	expected.Signon.Org = "BNK"
 | 
			
		||||
	expected.Signon.Fid = "1987"
 | 
			
		||||
 | 
			
		||||
	bankacctinfo := ofxgo.BankAcctInfo{
 | 
			
		||||
		BankAcctFrom: ofxgo.BankAcct{
 | 
			
		||||
	bankacctinfo := BankAcctInfo{
 | 
			
		||||
		BankAcctFrom: BankAcct{
 | 
			
		||||
			BankID:   "8367556009",
 | 
			
		||||
			AcctID:   "000999847",
 | 
			
		||||
			AcctType: ofxgo.AcctTypeMoneyMrkt,
 | 
			
		||||
			AcctType: AcctTypeMoneyMrkt,
 | 
			
		||||
		},
 | 
			
		||||
		SupTxDl:   true,
 | 
			
		||||
		XferSrc:   true,
 | 
			
		||||
		XferDest:  true,
 | 
			
		||||
		SvcStatus: ofxgo.SvcStatusActive,
 | 
			
		||||
		SvcStatus: SvcStatusActive,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	acctInfoResponse := ofxgo.AcctInfoResponse{
 | 
			
		||||
	acctInfoResponse := AcctInfoResponse{
 | 
			
		||||
		TrnUID: "10938754",
 | 
			
		||||
		Status: ofxgo.Status{
 | 
			
		||||
		Status: Status{
 | 
			
		||||
			Code:     0,
 | 
			
		||||
			Severity: "INFO",
 | 
			
		||||
		},
 | 
			
		||||
		DtAcctUp: *ofxgo.NewDateGMT(2005, 2, 28, 0, 0, 0, 0),
 | 
			
		||||
		AcctInfo: []ofxgo.AcctInfo{{
 | 
			
		||||
		DtAcctUp: *NewDateGMT(2005, 2, 28, 0, 0, 0, 0),
 | 
			
		||||
		AcctInfo: []AcctInfo{{
 | 
			
		||||
			Desc:         "Personal Checking",
 | 
			
		||||
			Phone:        "888-222-5827",
 | 
			
		||||
			BankAcctInfo: &bankacctinfo,
 | 
			
		||||
@@ -149,10 +148,11 @@ func TestUnmarshalAcctInfoResponse(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
	expected.Signup = append(expected.Signup, &acctInfoResponse)
 | 
			
		||||
 | 
			
		||||
	response, err := ofxgo.ParseResponse(responseReader)
 | 
			
		||||
	response, err := ParseResponse(responseReader)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error unmarshalling response: %s\n", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	checkResponsesEqual(t, &expected, response)
 | 
			
		||||
	checkResponseRoundTrip(t, response)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										143
									
								
								types_test.go
									
									
									
									
									
								
							
							
						
						
									
										143
									
								
								types_test.go
									
									
									
									
									
								
							@@ -1,9 +1,8 @@
 | 
			
		||||
package ofxgo_test
 | 
			
		||||
package ofxgo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/aclindsa/xml"
 | 
			
		||||
	"github.com/aclindsa/ofxgo"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -57,7 +56,7 @@ func unmarshalHelper(t *testing.T, input string, expected interface{}, overwritt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMarshalInt(t *testing.T) {
 | 
			
		||||
	var i ofxgo.Int = 927
 | 
			
		||||
	var i Int = 927
 | 
			
		||||
	marshalHelper(t, "927", &i)
 | 
			
		||||
	i = 0
 | 
			
		||||
	marshalHelper(t, "0", &i)
 | 
			
		||||
@@ -66,7 +65,7 @@ func TestMarshalInt(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUnmarshalInt(t *testing.T) {
 | 
			
		||||
	var i, overwritten ofxgo.Int = -48394, 0
 | 
			
		||||
	var i, overwritten Int = -48394, 0
 | 
			
		||||
	unmarshalHelper(t, "-48394", &i, &overwritten)
 | 
			
		||||
	i = 0
 | 
			
		||||
	unmarshalHelper(t, "0", &i, &overwritten)
 | 
			
		||||
@@ -78,7 +77,7 @@ func TestUnmarshalInt(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMarshalAmount(t *testing.T) {
 | 
			
		||||
	var a ofxgo.Amount
 | 
			
		||||
	var a Amount
 | 
			
		||||
 | 
			
		||||
	a.SetFrac64(8, 1)
 | 
			
		||||
	marshalHelper(t, "8", &a)
 | 
			
		||||
@@ -95,13 +94,13 @@ func TestMarshalAmount(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUnmarshalAmount(t *testing.T) {
 | 
			
		||||
	var a, overwritten ofxgo.Amount
 | 
			
		||||
	var a, overwritten Amount
 | 
			
		||||
 | 
			
		||||
	// Amount/big.Rat needs a special equality test because reflect.DeepEqual
 | 
			
		||||
	// doesn't always return equal for two values that big.Rat.Cmp() does
 | 
			
		||||
	eq := func(a, b interface{}) bool {
 | 
			
		||||
		if amountA, ok := a.(*ofxgo.Amount); ok {
 | 
			
		||||
			if amountB, ok2 := b.(*ofxgo.Amount); ok2 {
 | 
			
		||||
		if amountA, ok := a.(*Amount); ok {
 | 
			
		||||
			if amountB, ok2 := b.(*Amount); ok2 {
 | 
			
		||||
				return amountA.Cmp(&amountB.Rat) == 0
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -127,18 +126,18 @@ func TestUnmarshalAmount(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAmountEqual(t *testing.T) {
 | 
			
		||||
	assertEq := func(a, b ofxgo.Amount) {
 | 
			
		||||
	assertEq := func(a, b Amount) {
 | 
			
		||||
		if !a.Equal(b) {
 | 
			
		||||
			t.Fatalf("Amounts should be equal but Equal returned false: %s and %s\n", a, b)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	assertNEq := func(a, b ofxgo.Amount) {
 | 
			
		||||
	assertNEq := func(a, b Amount) {
 | 
			
		||||
		if a.Equal(b) {
 | 
			
		||||
			t.Fatalf("Amounts should not be equal but Equal returned true: %s and %s\n", a, b)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var a, b ofxgo.Amount
 | 
			
		||||
	var a, b Amount
 | 
			
		||||
	a.SetInt64(-19487135)
 | 
			
		||||
	b.SetInt64(-19487135)
 | 
			
		||||
	assertEq(a, b)
 | 
			
		||||
@@ -154,7 +153,7 @@ func TestAmountEqual(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMarshalDate(t *testing.T) {
 | 
			
		||||
	var d *ofxgo.Date
 | 
			
		||||
	var d *Date
 | 
			
		||||
	UTC := time.FixedZone("UTC", 0)
 | 
			
		||||
	GMT_nodesc := time.FixedZone("", 0)
 | 
			
		||||
	EST := time.FixedZone("EST", -5*60*60)
 | 
			
		||||
@@ -162,35 +161,35 @@ func TestMarshalDate(t *testing.T) {
 | 
			
		||||
	IST := time.FixedZone("IST", (5*60+30)*60)
 | 
			
		||||
	NST := time.FixedZone("NST", -(3*60+30)*60)
 | 
			
		||||
 | 
			
		||||
	d = ofxgo.NewDateGMT(2017, 3, 14, 15, 9, 26, 53*1000*1000)
 | 
			
		||||
	d = NewDateGMT(2017, 3, 14, 15, 9, 26, 53*1000*1000)
 | 
			
		||||
	marshalHelper(t, "20170314150926.053[0:GMT]", d)
 | 
			
		||||
	d = ofxgo.NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, NPT)
 | 
			
		||||
	d = NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, NPT)
 | 
			
		||||
	marshalHelper(t, "20170314150926.053[5.75:NPT]", d)
 | 
			
		||||
	d = ofxgo.NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, EST)
 | 
			
		||||
	d = NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, EST)
 | 
			
		||||
	marshalHelper(t, "20170314150926.053[-5:EST]", d)
 | 
			
		||||
	d = ofxgo.NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, UTC)
 | 
			
		||||
	d = NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, UTC)
 | 
			
		||||
	marshalHelper(t, "20170314150926.053[0:UTC]", d)
 | 
			
		||||
	d = ofxgo.NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, IST)
 | 
			
		||||
	d = NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, IST)
 | 
			
		||||
	marshalHelper(t, "20170314150926.053[5.50:IST]", d)
 | 
			
		||||
	d = ofxgo.NewDate(9999, 11, 1, 23, 59, 59, 1000, EST)
 | 
			
		||||
	d = NewDate(9999, 11, 1, 23, 59, 59, 1000, EST)
 | 
			
		||||
	marshalHelper(t, "99991101235959.000[-5:EST]", d)
 | 
			
		||||
	d = ofxgo.NewDate(0, 1, 1, 0, 0, 0, 0, IST)
 | 
			
		||||
	d = NewDate(0, 1, 1, 0, 0, 0, 0, IST)
 | 
			
		||||
	marshalHelper(t, "00000101000000.000[5.50:IST]", d)
 | 
			
		||||
	d = &ofxgo.Date{Time: time.Unix(0, 0).In(UTC)}
 | 
			
		||||
	d = &Date{Time: time.Unix(0, 0).In(UTC)}
 | 
			
		||||
	marshalHelper(t, "19700101000000.000[0:UTC]", d)
 | 
			
		||||
	d = ofxgo.NewDate(2017, 3, 14, 0, 0, 26, 53*1000*1000, EST)
 | 
			
		||||
	d = NewDate(2017, 3, 14, 0, 0, 26, 53*1000*1000, EST)
 | 
			
		||||
	marshalHelper(t, "20170314000026.053[-5:EST]", d)
 | 
			
		||||
	d = ofxgo.NewDate(2017, 3, 14, 0, 0, 26, 53*1000*1000, NST)
 | 
			
		||||
	d = NewDate(2017, 3, 14, 0, 0, 26, 53*1000*1000, NST)
 | 
			
		||||
	marshalHelper(t, "20170314000026.053[-3.50:NST]", d)
 | 
			
		||||
 | 
			
		||||
	// Time zone without textual description
 | 
			
		||||
	d = ofxgo.NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, GMT_nodesc)
 | 
			
		||||
	d = NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, GMT_nodesc)
 | 
			
		||||
	marshalHelper(t, "20170314150926.053[0]", d)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUnmarshalDate(t *testing.T) {
 | 
			
		||||
	var d *ofxgo.Date
 | 
			
		||||
	var overwritten ofxgo.Date
 | 
			
		||||
	var d *Date
 | 
			
		||||
	var overwritten Date
 | 
			
		||||
	GMT := time.FixedZone("GMT", 0)
 | 
			
		||||
	EST := time.FixedZone("EST", -5*60*60)
 | 
			
		||||
	NPT := time.FixedZone("NPT", (5*60+45)*60)
 | 
			
		||||
@@ -199,8 +198,8 @@ func TestUnmarshalDate(t *testing.T) {
 | 
			
		||||
	NST_nodesc := time.FixedZone("", -(3*60+30)*60)
 | 
			
		||||
 | 
			
		||||
	eq := func(a, b interface{}) bool {
 | 
			
		||||
		if dateA, ok := a.(*ofxgo.Date); ok {
 | 
			
		||||
			if dateB, ok2 := b.(*ofxgo.Date); ok2 {
 | 
			
		||||
		if dateA, ok := a.(*Date); ok {
 | 
			
		||||
			if dateB, ok2 := b.(*Date); ok2 {
 | 
			
		||||
				return dateA.Equal(*dateB)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -208,14 +207,14 @@ func TestUnmarshalDate(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Ensure omitted fields default to the correct values
 | 
			
		||||
	d = ofxgo.NewDateGMT(2017, 3, 14, 15, 9, 26, 53*1000*1000)
 | 
			
		||||
	d = NewDateGMT(2017, 3, 14, 15, 9, 26, 53*1000*1000)
 | 
			
		||||
	unmarshalHelper2(t, "20170314150926.053[0]", d, &overwritten, eq)
 | 
			
		||||
	unmarshalHelper2(t, "20170314150926.053", d, &overwritten, eq)
 | 
			
		||||
	d = ofxgo.NewDate(2017, 3, 14, 0, 0, 0, 0, GMT)
 | 
			
		||||
	d = NewDate(2017, 3, 14, 0, 0, 0, 0, GMT)
 | 
			
		||||
	unmarshalHelper2(t, "20170314", d, &overwritten, eq)
 | 
			
		||||
 | 
			
		||||
	// Ensure all signs on time zone offsets are properly handled
 | 
			
		||||
	d = ofxgo.NewDateGMT(2017, 3, 14, 15, 9, 26, 53*1000*1000)
 | 
			
		||||
	d = NewDateGMT(2017, 3, 14, 15, 9, 26, 53*1000*1000)
 | 
			
		||||
	unmarshalHelper2(t, "20170314150926.053[0:GMT]", d, &overwritten, eq)
 | 
			
		||||
	unmarshalHelper2(t, "20170314150926.053[+0:GMT]", d, &overwritten, eq)
 | 
			
		||||
	unmarshalHelper2(t, "20170314150926.053[-0:GMT]", d, &overwritten, eq)
 | 
			
		||||
@@ -223,38 +222,38 @@ func TestUnmarshalDate(t *testing.T) {
 | 
			
		||||
	unmarshalHelper2(t, "20170314150926.053[+0]", d, &overwritten, eq)
 | 
			
		||||
	unmarshalHelper2(t, "20170314150926.053[-0]", d, &overwritten, eq)
 | 
			
		||||
 | 
			
		||||
	d = ofxgo.NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, NPT)
 | 
			
		||||
	d = NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, NPT)
 | 
			
		||||
	unmarshalHelper2(t, "20170314150926.053[5.75:NPT]", d, &overwritten, eq)
 | 
			
		||||
	d = ofxgo.NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, EST)
 | 
			
		||||
	d = NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, EST)
 | 
			
		||||
	unmarshalHelper2(t, "20170314150926.053[-5:EST]", d, &overwritten, eq)
 | 
			
		||||
	d = ofxgo.NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, GMT)
 | 
			
		||||
	d = NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, GMT)
 | 
			
		||||
	unmarshalHelper2(t, "20170314150926.053[0:GMT]", d, &overwritten, eq)
 | 
			
		||||
	d = ofxgo.NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, IST)
 | 
			
		||||
	d = NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, IST)
 | 
			
		||||
	unmarshalHelper2(t, "20170314150926.053[5.50:IST]", d, &overwritten, eq)
 | 
			
		||||
	d = ofxgo.NewDate(2018, 11, 1, 23, 59, 58, 0, EST)
 | 
			
		||||
	d = NewDate(2018, 11, 1, 23, 59, 58, 0, EST)
 | 
			
		||||
	unmarshalHelper2(t, "20181101235958.000[-5:EST]", d, &overwritten, eq)
 | 
			
		||||
	d = ofxgo.NewDate(0, 1, 1, 0, 0, 0, 0, IST)
 | 
			
		||||
	d = NewDate(0, 1, 1, 0, 0, 0, 0, IST)
 | 
			
		||||
	unmarshalHelper2(t, "00000101000000.000[5.50:IST]", d, &overwritten, eq)
 | 
			
		||||
	d = &ofxgo.Date{Time: time.Unix(0, 0).In(GMT)}
 | 
			
		||||
	d = &Date{Time: time.Unix(0, 0).In(GMT)}
 | 
			
		||||
	unmarshalHelper2(t, "19700101000000.000[0:GMT]", d, &overwritten, eq)
 | 
			
		||||
	d = ofxgo.NewDate(2017, 3, 14, 0, 0, 26, 53*1000*1000, EST)
 | 
			
		||||
	d = NewDate(2017, 3, 14, 0, 0, 26, 53*1000*1000, EST)
 | 
			
		||||
	unmarshalHelper2(t, "20170314000026.053[-5:EST]", d, &overwritten, eq)
 | 
			
		||||
	d = ofxgo.NewDate(2017, 3, 14, 0, 0, 26, 53*1000*1000, NST)
 | 
			
		||||
	d = NewDate(2017, 3, 14, 0, 0, 26, 53*1000*1000, NST)
 | 
			
		||||
	unmarshalHelper2(t, "20170314000026.053[-3.50:NST]", d, &overwritten, eq)
 | 
			
		||||
 | 
			
		||||
	// Autopopulate zone without textual description for GMT
 | 
			
		||||
	d = ofxgo.NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, GMT)
 | 
			
		||||
	d = NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, GMT)
 | 
			
		||||
	unmarshalHelper2(t, "20170314150926.053[0]", d, &overwritten, eq)
 | 
			
		||||
	// but not for others:
 | 
			
		||||
	d = ofxgo.NewDate(2017, 3, 14, 0, 0, 26, 53*1000*1000, NST_nodesc)
 | 
			
		||||
	d = NewDate(2017, 3, 14, 0, 0, 26, 53*1000*1000, NST_nodesc)
 | 
			
		||||
	unmarshalHelper2(t, "20170314000026.053[-3.50]", d, &overwritten, eq)
 | 
			
		||||
 | 
			
		||||
	// Make sure we handle poorly-formatted dates (from Vanguard)
 | 
			
		||||
	d = ofxgo.NewDate(2016, 12, 7, 16, 0, 0, 0, EST)
 | 
			
		||||
	d = NewDate(2016, 12, 7, 16, 0, 0, 0, EST)
 | 
			
		||||
	unmarshalHelper2(t, "20161207160000.000[-5:EST]610900.500[-9:BST]", d, &overwritten, eq) // extra part intentionally different to ensure the first timezone is parsed
 | 
			
		||||
 | 
			
		||||
	// Make sure we properly handle ending newlines
 | 
			
		||||
	d = ofxgo.NewDate(2018, 11, 1, 23, 59, 58, 0, EST)
 | 
			
		||||
	d = NewDate(2018, 11, 1, 23, 59, 58, 0, EST)
 | 
			
		||||
	unmarshalHelper2(t, "20181101235958.000[-5:EST]\n", d, &overwritten, eq)
 | 
			
		||||
	unmarshalHelper2(t, "20181101235958.000[-5:EST]\n\t", d, &overwritten, eq)
 | 
			
		||||
}
 | 
			
		||||
@@ -263,23 +262,23 @@ func TestDateEqual(t *testing.T) {
 | 
			
		||||
	GMT := time.FixedZone("GMT", 0)
 | 
			
		||||
	EST := time.FixedZone("EST", -5*60*60)
 | 
			
		||||
 | 
			
		||||
	assertEq := func(a, b *ofxgo.Date) {
 | 
			
		||||
	assertEq := func(a, b *Date) {
 | 
			
		||||
		if !a.Equal(*b) {
 | 
			
		||||
			t.Fatalf("Dates should be equal but Equal returned false: %s and %s\n", *a, *b)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	assertNEq := func(a, b *ofxgo.Date) {
 | 
			
		||||
	assertNEq := func(a, b *Date) {
 | 
			
		||||
		if a.Equal(*b) {
 | 
			
		||||
			t.Fatalf("Dates should not be equal but Equal returned true: %s and %s\n", *a, *b)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Ensure omitted fields default to the correct values
 | 
			
		||||
	gmt1 := ofxgo.NewDateGMT(2017, 3, 14, 15, 9, 26, 53*1000*1000)
 | 
			
		||||
	gmt2 := ofxgo.NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, GMT)
 | 
			
		||||
	est1 := ofxgo.NewDate(2017, 3, 14, 10, 9, 26, 53*1000*1000, EST)
 | 
			
		||||
	est2 := ofxgo.NewDate(2017, 3, 14, 10, 9, 26, 53*1000*1000+1, EST)
 | 
			
		||||
	est3 := ofxgo.NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, EST)
 | 
			
		||||
	gmt1 := NewDateGMT(2017, 3, 14, 15, 9, 26, 53*1000*1000)
 | 
			
		||||
	gmt2 := NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, GMT)
 | 
			
		||||
	est1 := NewDate(2017, 3, 14, 10, 9, 26, 53*1000*1000, EST)
 | 
			
		||||
	est2 := NewDate(2017, 3, 14, 10, 9, 26, 53*1000*1000+1, EST)
 | 
			
		||||
	est3 := NewDate(2017, 3, 14, 15, 9, 26, 53*1000*1000, EST)
 | 
			
		||||
 | 
			
		||||
	assertEq(gmt1, gmt2)
 | 
			
		||||
	assertEq(gmt2, gmt1)
 | 
			
		||||
@@ -291,7 +290,7 @@ func TestDateEqual(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMarshalString(t *testing.T) {
 | 
			
		||||
	var s ofxgo.String = ""
 | 
			
		||||
	var s String = ""
 | 
			
		||||
	marshalHelper(t, "", &s)
 | 
			
		||||
	s = "foo&bar"
 | 
			
		||||
	marshalHelper(t, "foo&bar", &s)
 | 
			
		||||
@@ -302,7 +301,7 @@ func TestMarshalString(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUnmarshalString(t *testing.T) {
 | 
			
		||||
	var s, overwritten ofxgo.String = "", ""
 | 
			
		||||
	var s, overwritten String = "", ""
 | 
			
		||||
	unmarshalHelper(t, "", &s, &overwritten)
 | 
			
		||||
	s = "foo&bar"
 | 
			
		||||
	unmarshalHelper(t, "foo&bar", &s, &overwritten)
 | 
			
		||||
@@ -318,14 +317,14 @@ func TestUnmarshalString(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMarshalBoolean(t *testing.T) {
 | 
			
		||||
	var b ofxgo.Boolean = true
 | 
			
		||||
	var b Boolean = true
 | 
			
		||||
	marshalHelper(t, "Y", &b)
 | 
			
		||||
	b = false
 | 
			
		||||
	marshalHelper(t, "N", &b)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUnmarshalBoolean(t *testing.T) {
 | 
			
		||||
	var b, overwritten ofxgo.Boolean = true, false
 | 
			
		||||
	var b, overwritten Boolean = true, false
 | 
			
		||||
	unmarshalHelper(t, "Y", &b, &overwritten)
 | 
			
		||||
	b = false
 | 
			
		||||
	unmarshalHelper(t, "N", &b, &overwritten)
 | 
			
		||||
@@ -335,12 +334,12 @@ func TestUnmarshalBoolean(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMarshalUID(t *testing.T) {
 | 
			
		||||
	var u ofxgo.UID = "d1cf3d3d-9ef9-4a97-b180-81706829cb04"
 | 
			
		||||
	var u UID = "d1cf3d3d-9ef9-4a97-b180-81706829cb04"
 | 
			
		||||
	marshalHelper(t, "d1cf3d3d-9ef9-4a97-b180-81706829cb04", &u)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUnmarshalUID(t *testing.T) {
 | 
			
		||||
	var u, overwritten ofxgo.UID = "d1cf3d3d-9ef9-4a97-b180-81706829cb04", ""
 | 
			
		||||
	var u, overwritten UID = "d1cf3d3d-9ef9-4a97-b180-81706829cb04", ""
 | 
			
		||||
	unmarshalHelper(t, "d1cf3d3d-9ef9-4a97-b180-81706829cb04", &u, &overwritten)
 | 
			
		||||
	// Make sure stray newlines are handled properly
 | 
			
		||||
	u = "0f94ce83-13b7-7568-e4fc-c02c7b47e7ab"
 | 
			
		||||
@@ -349,7 +348,7 @@ func TestUnmarshalUID(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUIDRecommendedFormat(t *testing.T) {
 | 
			
		||||
	var u ofxgo.UID = "d1cf3d3d-9ef9-4a97-b180-81706829cb04"
 | 
			
		||||
	var u UID = "d1cf3d3d-9ef9-4a97-b180-81706829cb04"
 | 
			
		||||
	if ok, err := u.RecommendedFormat(); !ok || err != nil {
 | 
			
		||||
		t.Fatalf("UID unexpectedly failed validation\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -368,7 +367,7 @@ func TestUIDRecommendedFormat(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUIDValid(t *testing.T) {
 | 
			
		||||
	var u ofxgo.UID = ""
 | 
			
		||||
	var u UID = ""
 | 
			
		||||
	if ok, err := u.Valid(); ok || err == nil {
 | 
			
		||||
		t.Fatalf("Empty UID unexpectedly valid\n")
 | 
			
		||||
	}
 | 
			
		||||
@@ -383,7 +382,7 @@ func TestUIDValid(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRandomUID(t *testing.T) {
 | 
			
		||||
	uid, err := ofxgo.RandomUID()
 | 
			
		||||
	uid, err := RandomUID()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error when calling RandomUID: %s\n", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -393,46 +392,46 @@ func TestRandomUID(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMarshalCurrSymbol(t *testing.T) {
 | 
			
		||||
	c, _ := ofxgo.NewCurrSymbol("USD")
 | 
			
		||||
	c, _ := NewCurrSymbol("USD")
 | 
			
		||||
	marshalHelper(t, "USD", &c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUnmarshalCurrSymbol(t *testing.T) {
 | 
			
		||||
	var overwritten ofxgo.CurrSymbol
 | 
			
		||||
	c, _ := ofxgo.NewCurrSymbol("USD")
 | 
			
		||||
	var overwritten CurrSymbol
 | 
			
		||||
	c, _ := NewCurrSymbol("USD")
 | 
			
		||||
	unmarshalHelper(t, "USD", c, &overwritten)
 | 
			
		||||
	// Make sure stray newlines are handled properly
 | 
			
		||||
	c, _ = ofxgo.NewCurrSymbol("EUR")
 | 
			
		||||
	c, _ = NewCurrSymbol("EUR")
 | 
			
		||||
	unmarshalHelper(t, "EUR\n", c, &overwritten)
 | 
			
		||||
	unmarshalHelper(t, "EUR\n\t", c, &overwritten)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCurrSymbolEqual(t *testing.T) {
 | 
			
		||||
	usd1, _ := ofxgo.NewCurrSymbol("USD")
 | 
			
		||||
	usd2, _ := ofxgo.NewCurrSymbol("USD")
 | 
			
		||||
	usd1, _ := NewCurrSymbol("USD")
 | 
			
		||||
	usd2, _ := NewCurrSymbol("USD")
 | 
			
		||||
	if !usd1.Equal(*usd2) {
 | 
			
		||||
		t.Fatalf("Two \"USD\" CurrSymbols returned !Equal()\n")
 | 
			
		||||
	}
 | 
			
		||||
	xxx, _ := ofxgo.NewCurrSymbol("XXX")
 | 
			
		||||
	xxx, _ := NewCurrSymbol("XXX")
 | 
			
		||||
	if usd1.Equal(*xxx) {
 | 
			
		||||
		t.Fatalf("\"USD\" and \"XXX\" CurrSymbols returned Equal()\n")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCurrSymbolValid(t *testing.T) {
 | 
			
		||||
	var initial ofxgo.CurrSymbol
 | 
			
		||||
	var initial CurrSymbol
 | 
			
		||||
	ok, err := initial.Valid()
 | 
			
		||||
	if ok || err == nil {
 | 
			
		||||
		t.Fatalf("CurrSymbol unexpectedly returned Valid() for initial value\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ars, _ := ofxgo.NewCurrSymbol("ARS")
 | 
			
		||||
	ars, _ := NewCurrSymbol("ARS")
 | 
			
		||||
	ok, err = ars.Valid()
 | 
			
		||||
	if !ok || err != nil {
 | 
			
		||||
		t.Fatalf("CurrSymbol unexpectedly returned !Valid() for \"ARS\": %s\n", err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	xxx, _ := ofxgo.NewCurrSymbol("XXX")
 | 
			
		||||
	xxx, _ := NewCurrSymbol("XXX")
 | 
			
		||||
	ok, err = xxx.Valid()
 | 
			
		||||
	if ok || err == nil {
 | 
			
		||||
		t.Fatalf("CurrSymbol unexpectedly returned Valid() for \"XXX\"\n")
 | 
			
		||||
@@ -440,21 +439,21 @@ func TestCurrSymbolValid(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewCurrSymbol(t *testing.T) {
 | 
			
		||||
	curr, err := ofxgo.NewCurrSymbol("GBP")
 | 
			
		||||
	curr, err := NewCurrSymbol("GBP")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error calling NewCurrSymbol: %s\n", err)
 | 
			
		||||
	}
 | 
			
		||||
	if curr.String() != "GBP" {
 | 
			
		||||
		t.Fatalf("Created CurrSymbol doesn't print \"GBP\" as string representation\n")
 | 
			
		||||
	}
 | 
			
		||||
	curr, err = ofxgo.NewCurrSymbol("AFN")
 | 
			
		||||
	curr, err = NewCurrSymbol("AFN")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error calling NewCurrSymbol: %s\n", err)
 | 
			
		||||
	}
 | 
			
		||||
	if curr.String() != "AFN" {
 | 
			
		||||
		t.Fatalf("Created CurrSymbol doesn't print \"AFN\" as string representation\n")
 | 
			
		||||
	}
 | 
			
		||||
	curr, err = ofxgo.NewCurrSymbol("BLAH")
 | 
			
		||||
	curr, err = NewCurrSymbol("BLAH")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatalf("NewCurrSymbol didn't error on invalid currency identifier\n")
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										79
									
								
								vanguard_client.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								vanguard_client.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
package ofxgo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// VanguardClient provides a Client implementation which handles Vanguard's
 | 
			
		||||
// cookie-passing requirements. VanguardClient uses default, non-zero settings,
 | 
			
		||||
// if its fields are not initialized.
 | 
			
		||||
type VanguardClient struct {
 | 
			
		||||
	*BasicClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewVanguardClient returns a Client interface configured to handle Vanguard's
 | 
			
		||||
// brand of idiosyncracy
 | 
			
		||||
func NewVanguardClient(bc *BasicClient) Client {
 | 
			
		||||
	return &VanguardClient{bc}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// rawRequestCookies is RawRequest with the added feature of sending cookies
 | 
			
		||||
func rawRequestCookies(URL string, r io.Reader, cookies []*http.Cookie) (*http.Response, error) {
 | 
			
		||||
	if !strings.HasPrefix(URL, "https://") {
 | 
			
		||||
		return nil, errors.New("Refusing to send OFX request with possible plain-text password over non-https protocol")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	request, err := http.NewRequest("POST", URL, r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	request.Header.Set("Content-Type", "application/x-ofx")
 | 
			
		||||
	for _, cookie := range cookies {
 | 
			
		||||
		request.AddCookie(cookie)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	response, err := http.DefaultClient.Do(request)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if response.StatusCode != 200 {
 | 
			
		||||
		return nil, errors.New("OFXQuery request status: " + response.Status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return response, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *VanguardClient) RequestNoParse(r *Request) (*http.Response, error) {
 | 
			
		||||
	r.SetClientFields(c)
 | 
			
		||||
 | 
			
		||||
	b, err := r.Marshal()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	response, err := c.RawRequest(r.URL, b)
 | 
			
		||||
 | 
			
		||||
	// Some financial institutions (cough, Vanguard, cough), require a cookie
 | 
			
		||||
	// to be set on the http request, or they return empty responses.
 | 
			
		||||
	// Fortunately, the initial response contains the cookie we need, so if we
 | 
			
		||||
	// detect an empty response with cookies set that didn't have any errors,
 | 
			
		||||
	// re-try the request while sending their cookies back to them.
 | 
			
		||||
	if err == nil && response.ContentLength <= 0 && len(response.Cookies()) > 0 {
 | 
			
		||||
		b, err = r.Marshal()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return rawRequestCookies(r.URL, b, response.Cookies())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return response, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *VanguardClient) Request(r *Request) (*Response, error) {
 | 
			
		||||
	return clientRequest(c, r)
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user