1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
namespace OfxParserTest; |
4
|
|
|
|
5
|
|
|
use PHPUnit\Framework\TestCase; |
6
|
|
|
use OfxParser\Parser; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* @covers OfxParser\Parser |
10
|
|
|
*/ |
11
|
|
|
final class ParserTest extends TestCase |
12
|
|
|
{ |
13
|
|
|
public function testCreditCardStatementTransactionsAreLoaded(): void |
14
|
|
|
{ |
15
|
|
|
$parser = new Parser(); |
16
|
|
|
$ofx = $parser->loadFromFile(__DIR__ . '/../fixtures/ofxdata-credit-card.ofx'); |
17
|
|
|
|
18
|
|
|
$account = reset($ofx->bankAccounts); |
19
|
|
|
self::assertSame('1234567891234567', (string)$account->accountNumber); |
20
|
|
|
} |
21
|
|
|
|
22
|
|
|
public function testParseHeader(): void |
23
|
|
|
{ |
24
|
|
|
$parser = new Parser(); |
25
|
|
|
$ofx = $parser->loadFromFile(__DIR__ . '/../fixtures/ofxdata.ofx'); |
26
|
|
|
|
27
|
|
|
$header = [ |
28
|
|
|
'OFXHEADER' => '100', |
29
|
|
|
'DATA' => 'OFXSGML', |
30
|
|
|
'VERSION' => '103', |
31
|
|
|
'SECURITY' => 'NONE', |
32
|
|
|
'ENCODING' => 'USASCII', |
33
|
|
|
'CHARSET' => '1252', |
34
|
|
|
'COMPRESSION' => 'NONE', |
35
|
|
|
'OLDFILEUID' => 'NONE', |
36
|
|
|
'NEWFILEUID' => 'NONE', |
37
|
|
|
]; |
38
|
|
|
|
39
|
|
|
self::assertEquals($header, $ofx->header); |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
public function testParseXMLHeader(): void |
43
|
|
|
{ |
44
|
|
|
$parser = new Parser(); |
45
|
|
|
$ofx = $parser->loadFromFile(__DIR__ . '/../fixtures/ofxdata-xml.ofx'); |
46
|
|
|
|
47
|
|
|
$header = [ |
48
|
|
|
'OFXHEADER' => '200', |
49
|
|
|
'VERSION' => '200', |
50
|
|
|
'SECURITY' => 'NONE', |
51
|
|
|
'OLDFILEUID' => 'NONE', |
52
|
|
|
'NEWFILEUID' => 'NONE', |
53
|
|
|
]; |
54
|
|
|
|
55
|
|
|
self::assertSame($header, $ofx->header); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
public function testXmlLoadStringThrowsExceptionWithInvalidXml() |
59
|
|
|
{ |
60
|
|
|
$invalidXml = '<invalid xml>'; |
61
|
|
|
|
62
|
|
|
$method = new \ReflectionMethod(Parser::class, 'xmlLoadString'); |
63
|
|
|
$method->setAccessible(true); |
64
|
|
|
|
65
|
|
|
try { |
66
|
|
|
$method->invoke(new Parser(), $invalidXml); |
67
|
|
|
} catch (\Exception $e) { |
68
|
|
|
if (stripos($e->getMessage(), 'Failed to parse OFX') !== false) { |
69
|
|
|
$this->assertTrue(true); |
70
|
|
|
return true; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
throw $e; |
74
|
|
|
} catch (\Throwable $e) { |
75
|
|
|
if (stripos($e->getMessage(), 'Failed to parse OFX') !== false) { |
76
|
|
|
$this->assertTrue(true); |
77
|
|
|
return true; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
throw $e; |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
self::fail('Method xmlLoadString did not raise an expected exception parsing an invalid XML string'); |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
public function testXmlLoadStringLoadsValidXml(): void |
87
|
|
|
{ |
88
|
|
|
$validXml = '<fooRoot><foo>bar</foo></fooRoot>'; |
89
|
|
|
|
90
|
|
|
$method = new \ReflectionMethod(Parser::class, 'xmlLoadString'); |
91
|
|
|
$method->setAccessible(true); |
92
|
|
|
|
93
|
|
|
$xml = $method->invoke(new Parser(), $validXml); |
94
|
|
|
|
95
|
|
|
self::assertInstanceOf(\SimpleXMLElement::class, $xml); |
96
|
|
|
self::assertSame('bar', (string)$xml->foo); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* @return array<int, array<string>> |
101
|
|
|
*/ |
102
|
|
|
public function closeUnclosedXmlTagsProvider(): array |
103
|
|
|
{ |
104
|
|
|
return [ |
105
|
|
|
['<SOMETHING>', '<SOMETHING>'], |
106
|
|
|
['<SOMETHING>foo</SOMETHING>', '<SOMETHING>foo'], |
107
|
|
|
['<SOMETHING>foo</SOMETHING>', '<SOMETHING>foo</SOMETHING>'], |
108
|
|
|
['<BANKID>XXXXX</BANKID>', '<BANKID>XXXXX</BANKID>'], |
109
|
|
|
['<ACCTID>XXXXXXXXXXX</ACCTID>', '<ACCTID>XXXXXXXXXXX</ACCTID>'], |
110
|
|
|
['<ACCTID>-198.98</ACCTID>', '<ACCTID>-198.98</ACCTID>'], |
111
|
|
|
['<ACCTID>-198.98</ACCTID>', '<ACCTID>-198.98'], |
112
|
|
|
['<MEMO></MEMO>', '<MEMO>'], |
113
|
|
|
]; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* @dataProvider closeUnclosedXmlTagsProvider |
118
|
|
|
* @param $expected |
119
|
|
|
* @param $input |
120
|
|
|
*/ |
121
|
|
|
public function testCloseUnclosedXmlTags(string $expected, string $input): void |
122
|
|
|
{ |
123
|
|
|
$method = new \ReflectionMethod(Parser::class, 'closeUnclosedXmlTags'); |
124
|
|
|
$method->setAccessible(true); |
125
|
|
|
|
126
|
|
|
$parser = new Parser(); |
127
|
|
|
|
128
|
|
|
self::assertEquals($expected, $method->invoke($parser, $input)); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* @return array<int, array<string>> |
133
|
|
|
*/ |
134
|
|
|
public function convertSgmlToXmlProvider(): array |
135
|
|
|
{ |
136
|
|
|
return [ |
137
|
|
|
[<<<HERE |
138
|
|
|
<SOMETHING> |
139
|
|
|
<FOO>bar |
140
|
|
|
<BAZ>bat</BAZ> |
141
|
|
|
</SOMETHING> |
142
|
|
|
HERE |
143
|
|
|
, <<<HERE |
144
|
|
|
<SOMETHING> |
145
|
|
|
<FOO>bar</FOO> |
146
|
|
|
<BAZ>bat</BAZ> |
147
|
|
|
</SOMETHING> |
148
|
|
|
HERE |
149
|
|
|
], [<<<HERE |
150
|
|
|
<BANKACCTFROM> |
151
|
|
|
<BANKID>XXXXX</BANKID> |
152
|
|
|
<BRANCHID>XXXXX</BRANCHID> |
153
|
|
|
<ACCTID>XXXXXXXXXXX</ACCTID> |
154
|
|
|
<ACCTTYPE>CHECKING</ACCTTYPE> |
155
|
|
|
</BANKACCTFROM> |
156
|
|
|
HERE |
157
|
|
|
,<<<HERE |
158
|
|
|
<BANKACCTFROM> |
159
|
|
|
<BANKID>XXXXX</BANKID> |
160
|
|
|
<BRANCHID>XXXXX</BRANCHID> |
161
|
|
|
<ACCTID>XXXXXXXXXXX</ACCTID> |
162
|
|
|
<ACCTTYPE>CHECKING</ACCTTYPE> |
163
|
|
|
</BANKACCTFROM> |
164
|
|
|
HERE |
165
|
|
|
],[<<<HERE |
166
|
|
|
<SOMETHING> |
167
|
|
|
<FOO>bar & restaurant |
168
|
|
|
<BAZ>bat</BAZ> |
169
|
|
|
</SOMETHING> |
170
|
|
|
HERE |
171
|
|
|
, <<<HERE |
172
|
|
|
<SOMETHING> |
173
|
|
|
<FOO>bar & restaurant</FOO> |
174
|
|
|
<BAZ>bat</BAZ> |
175
|
|
|
</SOMETHING> |
176
|
|
|
HERE |
177
|
|
|
], |
178
|
|
|
]; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* @dataProvider convertSgmlToXmlProvider |
183
|
|
|
*/ |
184
|
|
|
public function testConvertSgmlToXml($sgml, $expected): void |
185
|
|
|
{ |
186
|
|
|
$method = new \ReflectionMethod(Parser::class, 'convertSgmlToXml'); |
187
|
|
|
$method->setAccessible(true); |
188
|
|
|
|
189
|
|
|
self::assertEquals($expected, $method->invoke(new Parser, $sgml)); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
public function testLoadFromFileWhenFileDoesNotExist(): void |
193
|
|
|
{ |
194
|
|
|
$this->expectException(\InvalidArgumentException::class); |
195
|
|
|
|
196
|
|
|
$parser = new Parser(); |
197
|
|
|
$parser->loadFromFile('a non-existent file'); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* @dataProvider loadFromStringProvider |
202
|
|
|
*/ |
203
|
|
|
public function testLoadFromFileWhenFileDoesExist(string $filename): void |
204
|
|
|
{ |
205
|
|
|
if (!file_exists($filename)) { |
206
|
|
|
self::markTestSkipped('Could not find data file, cannot test loadFromFile method fully'); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** @var Parser|\PHPUnit_Framework_MockObject_MockObject $parser */ |
210
|
|
|
$parser = $this->getMockBuilder(Parser::class) |
|
|
|
|
211
|
|
|
->setMethods(['loadFromString']) |
212
|
|
|
->getMock(); |
213
|
|
|
$parser->expects(self::once())->method('loadFromString'); |
|
|
|
|
214
|
|
|
$parser->loadFromFile($filename); |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* @return array |
219
|
|
|
*/ |
220
|
|
|
public function loadFromStringProvider(): array |
221
|
|
|
{ |
222
|
|
|
return [ |
223
|
|
|
'ofxdata.ofx' => [dirname(__DIR__).'/fixtures/ofxdata.ofx'], |
224
|
|
|
'ofxdata-oneline.ofx' => [dirname(__DIR__).'/fixtures/ofxdata-oneline.ofx'], |
225
|
|
|
'ofxdata-cmfr.ofx' => [dirname(__DIR__).'/fixtures/ofxdata-cmfr.ofx'], |
226
|
|
|
'ofxdata-bb.ofx' => [dirname(__DIR__).'/fixtures/ofxdata-bb.ofx'], |
227
|
|
|
'ofxdata-bb-two-stmtrs.ofx' => [dirname(__DIR__).'/fixtures/ofxdata-bb-two-stmtrs.ofx'], |
228
|
|
|
'ofxdata-credit-card.ofx' => [dirname(__DIR__).'/fixtures/ofxdata-credit-card.ofx'], |
229
|
|
|
'ofxdata-bpbfc.ofx' => [dirname(__DIR__).'/fixtures/ofxdata-bpbfc.ofx'], |
230
|
|
|
'ofxdata-memoWithQuotes.ofx' => [dirname(__DIR__).'/fixtures/ofxdata-memoWithQuotes.ofx'], |
231
|
|
|
'ofxdata-emptyDateTime.ofx' => [dirname(__DIR__).'/fixtures/ofxdata-emptyDateTime.ofx'], |
232
|
|
|
'ofxdata-memoWithAmpersand.ofx' => [dirname(__DIR__).'/fixtures/ofxdata-memoWithAmpersand.ofx'], |
233
|
|
|
'ofxdata-banking-xml200.ofx' => [dirname(__DIR__).'/fixtures/ofxdata-banking-xml200.ofx'], |
234
|
|
|
]; |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* @param string $filename |
239
|
|
|
* @throws \Exception |
240
|
|
|
* @dataProvider loadFromStringProvider |
241
|
|
|
*/ |
242
|
|
|
public function testLoadFromString($filename) |
243
|
|
|
{ |
244
|
|
|
if (!file_exists($filename)) { |
245
|
|
|
self::markTestSkipped('Could not find data file, cannot test loadFromString method fully'); |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
$content = file_get_contents($filename); |
249
|
|
|
|
250
|
|
|
$parser = new Parser(); |
251
|
|
|
|
252
|
|
|
try { |
253
|
|
|
$parser->loadFromString($content); |
254
|
|
|
} catch (\Exception $e) { |
255
|
|
|
throw $e; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
$this->assertTrue(true); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
public function testXmlLoadStringWithSelfClosingTag() |
262
|
|
|
{ |
263
|
|
|
$parser = new Parser(); |
264
|
|
|
|
265
|
|
|
try { |
266
|
|
|
$ofx = $parser->loadFromFile(__DIR__ . '/../fixtures/ofxdata-selfclose.ofx'); |
|
|
|
|
267
|
|
|
} catch (\RuntimeException $e) { |
268
|
|
|
if (stripos($e->getMessage(), 'Failed to parse OFX') !== false) { |
269
|
|
|
self::assertTrue(false, 'Xml with invalid self closing tag'); |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
self::assertTrue(true); |
274
|
|
|
} |
275
|
|
|
} |
276
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.