AbstractMimeEntityTest.php 37KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103
  1. <?php
  2. require_once 'Swift/Mime/MimeEntity.php';
  3. require_once 'Swift/Tests/SwiftUnitTestCase.php';
  4. require_once 'Swift/Mime/ContentEncoder.php';
  5. require_once 'Swift/Mime/Header.php';
  6. require_once 'Swift/Mime/ParameterizedHeader.php';
  7. require_once 'Swift/KeyCache.php';
  8. require_once 'Swift/Mime/HeaderSet.php';
  9. abstract class Swift_Mime_AbstractMimeEntityTest
  10. extends Swift_Tests_SwiftUnitTestCase
  11. {
  12. public function testGetHeadersReturnsHeaderSet()
  13. {
  14. $headers = $this->_createHeaderSet();
  15. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  16. $this->_createCache()
  17. );
  18. $this->assertSame($headers, $entity->getHeaders());
  19. }
  20. public function testContentTypeIsReturnedFromHeader()
  21. {
  22. $ctype = $this->_createHeader('Content-Type', 'image/jpeg-test');
  23. $headers = $this->_createHeaderSet(array('Content-Type' => $ctype));
  24. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  25. $this->_createCache()
  26. );
  27. $this->assertEqual('image/jpeg-test', $entity->getContentType());
  28. }
  29. public function testContentTypeIsSetInHeader()
  30. {
  31. $ctype = $this->_createHeader('Content-Type', 'text/plain', array(), false);
  32. $headers = $this->_createHeaderSet(array('Content-Type' => $ctype));
  33. $this->_checking(Expectations::create()
  34. -> one($ctype)->setFieldBodyModel('image/jpeg')
  35. -> ignoring($ctype)
  36. );
  37. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  38. $this->_createCache()
  39. );
  40. $entity->setContentType('image/jpeg');
  41. }
  42. public function testContentTypeHeaderIsAddedIfNoneSet()
  43. {
  44. $headers = $this->_createHeaderSet(array(), false);
  45. $this->_checking(Expectations::create()
  46. -> one($headers)->addParameterizedHeader('Content-Type', 'image/jpeg')
  47. -> ignoring($headers)
  48. );
  49. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  50. $this->_createCache()
  51. );
  52. $entity->setContentType('image/jpeg');
  53. }
  54. public function testContentTypeCanBeSetViaSetBody()
  55. {
  56. $headers = $this->_createHeaderSet(array(), false);
  57. $this->_checking(Expectations::create()
  58. -> one($headers)->addParameterizedHeader('Content-Type', 'text/html')
  59. -> ignoring($headers)
  60. );
  61. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  62. $this->_createCache()
  63. );
  64. $entity->setBody('<b>foo</b>', 'text/html');
  65. }
  66. public function testGetEncoderFromConstructor()
  67. {
  68. $encoder = $this->_createEncoder('base64');
  69. $entity = $this->_createEntity($this->_createHeaderSet(), $encoder,
  70. $this->_createCache()
  71. );
  72. $this->assertSame($encoder, $entity->getEncoder());
  73. }
  74. public function testSetAndGetEncoder()
  75. {
  76. $encoder = $this->_createEncoder('base64');
  77. $headers = $this->_createHeaderSet();
  78. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  79. $this->_createCache()
  80. );
  81. $entity->setEncoder($encoder);
  82. $this->assertSame($encoder, $entity->getEncoder());
  83. }
  84. public function testSettingEncoderUpdatesTransferEncoding()
  85. {
  86. $encoder = $this->_createEncoder('base64');
  87. $encoding = $this->_createHeader(
  88. 'Content-Transfer-Encoding', '8bit', array(), false
  89. );
  90. $headers = $this->_createHeaderSet(array(
  91. 'Content-Transfer-Encoding' => $encoding
  92. ));
  93. $this->_checking(Expectations::create()
  94. -> one($encoding)->setFieldBodyModel('base64')
  95. -> ignoring($encoding)
  96. );
  97. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  98. $this->_createCache()
  99. );
  100. $entity->setEncoder($encoder);
  101. }
  102. public function testSettingEncoderAddsEncodingHeaderIfNonePresent()
  103. {
  104. $headers = $this->_createHeaderSet(array(), false);
  105. $this->_checking(Expectations::create()
  106. -> one($headers)->addTextHeader('Content-Transfer-Encoding', 'something')
  107. -> ignoring($headers)
  108. );
  109. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  110. $this->_createCache()
  111. );
  112. $entity->setEncoder($this->_createEncoder('something'));
  113. }
  114. public function testIdIsReturnedFromHeader()
  115. {
  116. /* -- RFC 2045, 7.
  117. In constructing a high-level user agent, it may be desirable to allow
  118. one body to make reference to another. Accordingly, bodies may be
  119. labelled using the "Content-ID" header field, which is syntactically
  120. identical to the "Message-ID" header field
  121. */
  122. $cid = $this->_createHeader('Content-ID', 'zip@button');
  123. $headers = $this->_createHeaderSet(array('Content-ID' => $cid));
  124. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  125. $this->_createCache()
  126. );
  127. $this->assertEqual('zip@button', $entity->getId());
  128. }
  129. public function testIdIsSetInHeader()
  130. {
  131. $cid = $this->_createHeader('Content-ID', 'zip@button', array(), false);
  132. $headers = $this->_createHeaderSet(array('Content-ID' => $cid));
  133. $this->_checking(Expectations::create()
  134. -> one($cid)->setFieldBodyModel('foo@bar')
  135. -> ignoring($cid)
  136. );
  137. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  138. $this->_createCache()
  139. );
  140. $entity->setId('foo@bar');
  141. }
  142. public function testIdIsAutoGenerated()
  143. {
  144. $entity = $this->_createEntity($this->_createHeaderSet(),
  145. $this->_createEncoder(), $this->_createCache()
  146. );
  147. $this->assertPattern('/^.*?@.*?$/D', $entity->getId());
  148. }
  149. public function testGenerateIdCreatesNewId()
  150. {
  151. $headers = $this->_createHeaderSet();
  152. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  153. $this->_createCache()
  154. );
  155. $id1 = $entity->generateId();
  156. $id2 = $entity->generateId();
  157. $this->assertNotEqual($id1, $id2);
  158. }
  159. public function testGenerateIdSetsNewId()
  160. {
  161. $headers = $this->_createHeaderSet();
  162. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  163. $this->_createCache()
  164. );
  165. $id = $entity->generateId();
  166. $this->assertEqual($id, $entity->getId());
  167. }
  168. public function testDescriptionIsReadFromHeader()
  169. {
  170. /* -- RFC 2045, 8.
  171. The ability to associate some descriptive information with a given
  172. body is often desirable. For example, it may be useful to mark an
  173. "image" body as "a picture of the Space Shuttle Endeavor." Such text
  174. may be placed in the Content-Description header field. This header
  175. field is always optional.
  176. */
  177. $desc = $this->_createHeader('Content-Description', 'something');
  178. $headers = $this->_createHeaderSet(array('Content-Description' => $desc));
  179. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  180. $this->_createCache()
  181. );
  182. $this->assertEqual('something', $entity->getDescription());
  183. }
  184. public function testDescriptionIsSetInHeader()
  185. {
  186. $desc = $this->_createHeader('Content-Description', '', array(), false);
  187. $headers = $this->_createHeaderSet(array('Content-Description' => $desc));
  188. $this->_checking(Expectations::create()
  189. -> one($desc)->setFieldBodyModel('whatever')
  190. -> ignoring($desc)
  191. );
  192. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  193. $this->_createCache()
  194. );
  195. $entity->setDescription('whatever');
  196. }
  197. public function testDescriptionHeaderIsAddedIfNotPresent()
  198. {
  199. $headers = $this->_createHeaderSet(array(), false);
  200. $this->_checking(Expectations::create()
  201. -> one($headers)->addTextHeader('Content-Description', 'whatever')
  202. -> ignoring($headers)
  203. );
  204. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  205. $this->_createCache()
  206. );
  207. $entity->setDescription('whatever');
  208. }
  209. public function testSetAndGetMaxLineLength()
  210. {
  211. $entity = $this->_createEntity($this->_createHeaderSet(),
  212. $this->_createEncoder(), $this->_createCache()
  213. );
  214. $entity->setMaxLineLength(60);
  215. $this->assertEqual(60, $entity->getMaxLineLength());
  216. }
  217. public function testEncoderIsUsedForStringGeneration()
  218. {
  219. $encoder = $this->_createEncoder('base64', false);
  220. $this->_checking(Expectations::create()
  221. -> one($encoder)->encodeString('blah', optional())
  222. -> ignoring($encoder)
  223. );
  224. $entity = $this->_createEntity($this->_createHeaderSet(),
  225. $encoder, $this->_createCache()
  226. );
  227. $entity->setBody("blah");
  228. $entity->toString();
  229. }
  230. public function testMaxLineLengthIsProvidedWhenEncoding()
  231. {
  232. $encoder = $this->_createEncoder('base64', false);
  233. $this->_checking(Expectations::create()
  234. -> one($encoder)->encodeString('blah', 0, 65)
  235. -> ignoring($encoder)
  236. );
  237. $entity = $this->_createEntity($this->_createHeaderSet(),
  238. $encoder, $this->_createCache()
  239. );
  240. $entity->setBody("blah");
  241. $entity->setMaxLineLength(65);
  242. $entity->toString();
  243. }
  244. public function testHeadersAppearInString()
  245. {
  246. $headers = $this->_createHeaderSet(array(), false);
  247. $this->_checking(Expectations::create()
  248. -> ignoring($headers)->toString() -> returns(
  249. "Content-Type: text/plain; charset=utf-8\r\n" .
  250. "X-MyHeader: foobar\r\n"
  251. )
  252. -> ignoring($headers)
  253. );
  254. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  255. $this->_createCache()
  256. );
  257. $this->assertEqual(
  258. "Content-Type: text/plain; charset=utf-8\r\n" .
  259. "X-MyHeader: foobar\r\n",
  260. $entity->toString()
  261. );
  262. }
  263. public function testSetAndGetBody()
  264. {
  265. $entity = $this->_createEntity($this->_createHeaderSet(),
  266. $this->_createEncoder(), $this->_createCache()
  267. );
  268. $entity->setBody("blah\r\nblah!");
  269. $this->assertEqual("blah\r\nblah!", $entity->getBody());
  270. }
  271. public function testBodyIsAppended()
  272. {
  273. $headers = $this->_createHeaderSet(array(), false);
  274. $this->_checking(Expectations::create()
  275. -> ignoring($headers)->toString() -> returns(
  276. "Content-Type: text/plain; charset=utf-8\r\n"
  277. )
  278. -> ignoring($headers)
  279. );
  280. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  281. $this->_createCache()
  282. );
  283. $entity->setBody("blah\r\nblah!");
  284. $this->assertEqual(
  285. "Content-Type: text/plain; charset=utf-8\r\n" .
  286. "\r\n" .
  287. "blah\r\nblah!",
  288. $entity->toString()
  289. );
  290. }
  291. public function testGetBodyReturnsStringFromByteStream()
  292. {
  293. $os = $this->_createOutputStream("byte stream string");
  294. $entity = $this->_createEntity($this->_createHeaderSet(),
  295. $this->_createEncoder(), $this->_createCache()
  296. );
  297. $entity->setBody($os);
  298. $this->assertEqual("byte stream string", $entity->getBody());
  299. }
  300. public function testByteStreamBodyIsAppended()
  301. {
  302. $headers = $this->_createHeaderSet(array(), false);
  303. $os = $this->_createOutputStream("streamed");
  304. $this->_checking(Expectations::create()
  305. -> ignoring($headers)->toString() -> returns(
  306. "Content-Type: text/plain; charset=utf-8\r\n"
  307. )
  308. -> ignoring($headers)
  309. );
  310. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  311. $this->_createCache()
  312. );
  313. $entity->setBody($os);
  314. $this->assertEqual(
  315. "Content-Type: text/plain; charset=utf-8\r\n" .
  316. "\r\n" .
  317. "streamed",
  318. $entity->toString()
  319. );
  320. }
  321. public function testBoundaryCanBeRetrieved()
  322. {
  323. /* -- RFC 2046, 5.1.1.
  324. boundary := 0*69<bchars> bcharsnospace
  325. bchars := bcharsnospace / " "
  326. bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
  327. "+" / "_" / "," / "-" / "." /
  328. "/" / ":" / "=" / "?"
  329. */
  330. $entity = $this->_createEntity($this->_createHeaderSet(),
  331. $this->_createEncoder(), $this->_createCache()
  332. );
  333. $this->assertPattern(
  334. '/^[a-zA-Z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-zA-Z0-9\'\(\)\+_\-,\.\/:=\?]$/D',
  335. $entity->getBoundary()
  336. );
  337. }
  338. public function testBoundaryNeverChanges()
  339. {
  340. $entity = $this->_createEntity($this->_createHeaderSet(),
  341. $this->_createEncoder(), $this->_createCache()
  342. );
  343. $firstBoundary = $entity->getBoundary();
  344. for ($i = 0; $i < 10; $i++) {
  345. $this->assertEqual($firstBoundary, $entity->getBoundary());
  346. }
  347. }
  348. public function testBoundaryCanBeSet()
  349. {
  350. $entity = $this->_createEntity($this->_createHeaderSet(),
  351. $this->_createEncoder(), $this->_createCache()
  352. );
  353. $entity->setBoundary('foobar');
  354. $this->assertEqual('foobar', $entity->getBoundary());
  355. }
  356. public function testAddingChildrenGeneratesBoundaryInHeaders()
  357. {
  358. $child = $this->_createChild();
  359. $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false);
  360. $this->_checking(Expectations::create()
  361. -> one($cType)->setParameter('boundary', any())
  362. -> ignoring($cType)
  363. );
  364. $entity = $this->_createEntity($this->_createHeaderSet(array(
  365. 'Content-Type' => $cType
  366. )),
  367. $this->_createEncoder(), $this->_createCache()
  368. );
  369. $entity->setChildren(array($child));
  370. }
  371. public function testChildrenOfLevelAttachmentAndLessCauseMultipartMixed()
  372. {
  373. for ($level = Swift_Mime_MimeEntity::LEVEL_MIXED;
  374. $level > Swift_Mime_MimeEntity::LEVEL_TOP; $level /= 2)
  375. {
  376. $child = $this->_createChild($level);
  377. $cType = $this->_createHeader(
  378. 'Content-Type', 'text/plain', array(), false
  379. );
  380. $this->_checking(Expectations::create()
  381. -> one($cType)->setFieldBodyModel('multipart/mixed')
  382. -> ignoring($cType)
  383. );
  384. $entity = $this->_createEntity($this->_createHeaderSet(array(
  385. 'Content-Type' => $cType)),
  386. $this->_createEncoder(), $this->_createCache()
  387. );
  388. $entity->setChildren(array($child));
  389. }
  390. }
  391. public function testChildrenOfLevelAlternativeAndLessCauseMultipartAlternative()
  392. {
  393. for ($level = Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE;
  394. $level > Swift_Mime_MimeEntity::LEVEL_MIXED; $level /= 2)
  395. {
  396. $child = $this->_createChild($level);
  397. $cType = $this->_createHeader(
  398. 'Content-Type', 'text/plain', array(), false
  399. );
  400. $this->_checking(Expectations::create()
  401. -> one($cType)->setFieldBodyModel('multipart/alternative')
  402. -> ignoring($cType)
  403. );
  404. $entity = $this->_createEntity($this->_createHeaderSet(array(
  405. 'Content-Type' => $cType)),
  406. $this->_createEncoder(), $this->_createCache()
  407. );
  408. $entity->setChildren(array($child));
  409. }
  410. }
  411. public function testChildrenOfLevelRelatedAndLessCauseMultipartRelated()
  412. {
  413. for ($level = Swift_Mime_MimeEntity::LEVEL_RELATED;
  414. $level > Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE; $level /= 2)
  415. {
  416. $child = $this->_createChild($level);
  417. $cType = $this->_createHeader(
  418. 'Content-Type', 'text/plain', array(), false
  419. );
  420. $this->_checking(Expectations::create()
  421. -> one($cType)->setFieldBodyModel('multipart/related')
  422. -> ignoring($cType)
  423. );
  424. $entity = $this->_createEntity($this->_createHeaderSet(array(
  425. 'Content-Type' => $cType)),
  426. $this->_createEncoder(), $this->_createCache()
  427. );
  428. $entity->setChildren(array($child));
  429. }
  430. }
  431. public function testHighestLevelChildDeterminesContentType()
  432. {
  433. $combinations = array(
  434. array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED,
  435. Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
  436. Swift_Mime_MimeEntity::LEVEL_RELATED
  437. ),
  438. 'type' => 'multipart/mixed'
  439. ),
  440. array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED,
  441. Swift_Mime_MimeEntity::LEVEL_RELATED
  442. ),
  443. 'type' => 'multipart/mixed'
  444. ),
  445. array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED,
  446. Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE
  447. ),
  448. 'type' => 'multipart/mixed'
  449. ),
  450. array('levels' => array(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
  451. Swift_Mime_MimeEntity::LEVEL_RELATED
  452. ),
  453. 'type' => 'multipart/alternative'
  454. )
  455. );
  456. foreach ($combinations as $combination) {
  457. $children = array();
  458. foreach ($combination['levels'] as $level) {
  459. $children[] = $this->_createChild($level);
  460. }
  461. $cType = $this->_createHeader(
  462. 'Content-Type', 'text/plain', array(), false
  463. );
  464. $this->_checking(Expectations::create()
  465. -> one($cType)->setFieldBodyModel($combination['type'])
  466. -> ignoring($cType)
  467. );
  468. $entity = $this->_createEntity($this->_createHeaderSet(array(
  469. 'Content-Type' => $cType)),
  470. $this->_createEncoder(), $this->_createCache()
  471. );
  472. $entity->setChildren($children);
  473. }
  474. }
  475. public function testChildrenAppearNestedInString()
  476. {
  477. /* -- RFC 2046, 5.1.1.
  478. (excerpt too verbose to paste here)
  479. */
  480. $headers = $this->_createHeaderSet(array(), false);
  481. $child1 = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
  482. "Content-Type: text/plain\r\n" .
  483. "\r\n" .
  484. "foobar"
  485. );
  486. $child2 = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
  487. "Content-Type: text/html\r\n" .
  488. "\r\n" .
  489. "<b>foobar</b>"
  490. );
  491. $this->_checking(Expectations::create()
  492. -> ignoring($headers)->toString() -> returns(
  493. "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n"
  494. )
  495. -> ignoring($headers)
  496. );
  497. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  498. $this->_createCache()
  499. );
  500. $entity->setBoundary('xxx');
  501. $entity->setChildren(array($child1, $child2));
  502. $this->assertEqual(
  503. "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n" .
  504. "\r\n" .
  505. "\r\n--xxx\r\n" .
  506. "Content-Type: text/plain\r\n" .
  507. "\r\n" .
  508. "foobar\r\n" .
  509. "\r\n--xxx\r\n" .
  510. "Content-Type: text/html\r\n" .
  511. "\r\n" .
  512. "<b>foobar</b>\r\n" .
  513. "\r\n--xxx--\r\n",
  514. $entity->toString()
  515. );
  516. }
  517. public function testMixingLevelsIsHierarchical()
  518. {
  519. $headers = $this->_createHeaderSet(array(), false);
  520. $newHeaders = $this->_createHeaderSet(array(), false);
  521. $part = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
  522. "Content-Type: text/plain\r\n" .
  523. "\r\n" .
  524. "foobar"
  525. );
  526. $attachment = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_MIXED,
  527. "Content-Type: application/octet-stream\r\n" .
  528. "\r\n" .
  529. "data"
  530. );
  531. $this->_checking(Expectations::create()
  532. -> ignoring($headers)->toString() -> returns(
  533. "Content-Type: multipart/mixed; boundary=\"xxx\"\r\n"
  534. )
  535. -> ignoring($headers)->newInstance() -> returns($newHeaders)
  536. -> ignoring($headers)
  537. -> ignoring($newHeaders)->toString() -> returns(
  538. "Content-Type: multipart/alternative; boundary=\"yyy\"\r\n"
  539. )
  540. -> ignoring($newHeaders)
  541. );
  542. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  543. $this->_createCache()
  544. );
  545. $entity->setBoundary('xxx');
  546. $entity->setChildren(array($part, $attachment));
  547. $this->assertPattern(
  548. "~^" .
  549. "Content-Type: multipart/mixed; boundary=\"xxx\"\r\n" .
  550. "\r\n\r\n--xxx\r\n" .
  551. "Content-Type: multipart/alternative; boundary=\"yyy\"\r\n" .
  552. "\r\n\r\n--(.*?)\r\n" .
  553. "Content-Type: text/plain\r\n" .
  554. "\r\n" .
  555. "foobar" .
  556. "\r\n\r\n--\\1--\r\n" .
  557. "\r\n\r\n--xxx\r\n" .
  558. "Content-Type: application/octet-stream\r\n" .
  559. "\r\n" .
  560. "data" .
  561. "\r\n\r\n--xxx--\r\n" .
  562. "\$~",
  563. $entity->toString()
  564. );
  565. }
  566. public function testSettingEncoderNotifiesChildren()
  567. {
  568. $child = $this->_createChild(0, '', false);
  569. $encoder = $this->_createEncoder('base64');
  570. $this->_checking(Expectations::create()
  571. -> one($child)->encoderChanged($encoder)
  572. -> ignoring($child)
  573. );
  574. $entity = $this->_createEntity($this->_createHeaderSet(),
  575. $this->_createEncoder(), $this->_createCache()
  576. );
  577. $entity->setChildren(array($child));
  578. $entity->setEncoder($encoder);
  579. }
  580. public function testReceiptOfEncoderChangeNotifiesChildren()
  581. {
  582. $child = $this->_createChild(0, '', false);
  583. $encoder = $this->_createEncoder('base64');
  584. $this->_checking(Expectations::create()
  585. -> one($child)->encoderChanged($encoder)
  586. -> ignoring($child)
  587. );
  588. $entity = $this->_createEntity($this->_createHeaderSet(),
  589. $this->_createEncoder(), $this->_createCache()
  590. );
  591. $entity->setChildren(array($child));
  592. $entity->encoderChanged($encoder);
  593. }
  594. public function testReceiptOfCharsetChangeNotifiesChildren()
  595. {
  596. $child = $this->_createChild(0, '', false);
  597. $this->_checking(Expectations::create()
  598. -> one($child)->charsetChanged('windows-874')
  599. -> ignoring($child)
  600. );
  601. $entity = $this->_createEntity($this->_createHeaderSet(),
  602. $this->_createEncoder(), $this->_createCache()
  603. );
  604. $entity->setChildren(array($child));
  605. $entity->charsetChanged('windows-874');
  606. }
  607. public function testEntityIsWrittenToByteStream()
  608. {
  609. $entity = $this->_createEntity($this->_createHeaderSet(),
  610. $this->_createEncoder(), $this->_createCache()
  611. );
  612. $is = $this->_createInputStream(false);
  613. $this->_checking(Expectations::create()
  614. -> atLeast(1)->of($is)->write(any())
  615. -> ignoring($is)
  616. );
  617. $entity->toByteStream($is);
  618. }
  619. public function testEntityHeadersAreComittedToByteStream()
  620. {
  621. $entity = $this->_createEntity($this->_createHeaderSet(),
  622. $this->_createEncoder(), $this->_createCache()
  623. );
  624. $is = $this->_createInputStream(false);
  625. $this->_checking(Expectations::create()
  626. -> atLeast(1)->of($is)->commit()
  627. -> atLeast(1)->of($is)->write(any())
  628. -> ignoring($is)
  629. );
  630. $entity->toByteStream($is);
  631. }
  632. public function testOrderingTextBeforeHtml()
  633. {
  634. $htmlChild = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
  635. "Content-Type: text/html\r\n" .
  636. "\r\n" .
  637. "HTML PART",
  638. false
  639. );
  640. $textChild = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
  641. "Content-Type: text/plain\r\n" .
  642. "\r\n" .
  643. "TEXT PART",
  644. false
  645. );
  646. $headers = $this->_createHeaderSet(array(), false);
  647. $this->_checking(Expectations::create()
  648. -> ignoring($headers)->toString() -> returns(
  649. "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n"
  650. )
  651. -> ignoring($headers)
  652. -> ignoring($htmlChild)->getContentType() -> returns('text/html')
  653. -> ignoring($htmlChild)
  654. -> ignoring($textChild)->getContentType() -> returns('text/plain')
  655. -> ignoring($textChild)
  656. );
  657. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  658. $this->_createCache()
  659. );
  660. $entity->setBoundary('xxx');
  661. $entity->setChildren(array($htmlChild, $textChild));
  662. $this->assertEqual(
  663. "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n" .
  664. "\r\n\r\n--xxx\r\n" .
  665. "Content-Type: text/plain\r\n" .
  666. "\r\n" .
  667. "TEXT PART" .
  668. "\r\n\r\n--xxx\r\n" .
  669. "Content-Type: text/html\r\n" .
  670. "\r\n" .
  671. "HTML PART" .
  672. "\r\n\r\n--xxx--\r\n",
  673. $entity->toString()
  674. );
  675. }
  676. public function testUnsettingChildrenRestoresContentType()
  677. {
  678. $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false);
  679. $child = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE);
  680. $s = $this->_mockery()->sequence('Type setting');
  681. $this->_checking(Expectations::create()
  682. -> one($cType)->setFieldBodyModel('image/jpeg') -> inSequence($s)
  683. -> one($cType)->setFieldBodyModel('multipart/alternative') -> inSequence($s)
  684. -> one($cType)->setFieldBodyModel('image/jpeg') -> inSequence($s)
  685. -> ignoring($cType)
  686. );
  687. $entity = $this->_createEntity($this->_createHeaderSet(array(
  688. 'Content-Type' => $cType
  689. )),
  690. $this->_createEncoder(), $this->_createCache()
  691. );
  692. $entity->setContentType('image/jpeg');
  693. $entity->setChildren(array($child));
  694. $entity->setChildren(array());
  695. }
  696. public function testBodyIsReadFromCacheWhenUsingToStringIfPresent()
  697. {
  698. $headers = $this->_createHeaderSet(array(), false);
  699. $this->_checking(Expectations::create()
  700. -> ignoring($headers)->toString() -> returns(
  701. "Content-Type: text/plain; charset=utf-8\r\n"
  702. )
  703. -> ignoring($headers)
  704. );
  705. $cache = $this->_createCache(false);
  706. $this->_checking(Expectations::create()
  707. -> one($cache)->hasKey(any(), 'body') -> returns(true)
  708. -> one($cache)->getString(any(), 'body') -> returns("\r\ncache\r\ncache!")
  709. -> ignoring($cache)
  710. );
  711. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  712. $cache
  713. );
  714. $entity->setBody("blah\r\nblah!");
  715. $this->assertEqual(
  716. "Content-Type: text/plain; charset=utf-8\r\n" .
  717. "\r\n" .
  718. "cache\r\ncache!",
  719. $entity->toString()
  720. );
  721. }
  722. public function testBodyIsAddedToCacheWhenUsingToString()
  723. {
  724. $headers = $this->_createHeaderSet(array(), false);
  725. $this->_checking(Expectations::create()
  726. -> ignoring($headers)->toString() -> returns(
  727. "Content-Type: text/plain; charset=utf-8\r\n"
  728. )
  729. -> ignoring($headers)
  730. );
  731. $cache = $this->_createCache(false);
  732. $this->_checking(Expectations::create()
  733. -> one($cache)->hasKey(any(), 'body') -> returns(false)
  734. -> one($cache)->setString(any(), 'body', "\r\nblah\r\nblah!", Swift_KeyCache::MODE_WRITE)
  735. -> ignoring($cache)
  736. );
  737. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  738. $cache
  739. );
  740. $entity->setBody("blah\r\nblah!");
  741. $entity->toString();
  742. }
  743. public function testBodyIsClearedFromCacheIfNewBodySet()
  744. {
  745. $headers = $this->_createHeaderSet(array(), false);
  746. $this->_checking(Expectations::create()
  747. -> ignoring($headers)->toString() -> returns(
  748. "Content-Type: text/plain; charset=utf-8\r\n"
  749. )
  750. -> ignoring($headers)
  751. );
  752. $cache = $this->_createCache(false);
  753. $this->_checking(Expectations::create()
  754. -> one($cache)->clearKey(any(), 'body')
  755. -> ignoring($cache)
  756. );
  757. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  758. $cache
  759. );
  760. $entity->setBody("blah\r\nblah!");
  761. $entity->toString();
  762. $entity->setBody("new\r\nnew!");
  763. }
  764. public function testBodyIsNotClearedFromCacheIfSameBodySet()
  765. {
  766. $headers = $this->_createHeaderSet(array(), false);
  767. $this->_checking(Expectations::create()
  768. -> ignoring($headers)->toString() -> returns(
  769. "Content-Type: text/plain; charset=utf-8\r\n"
  770. )
  771. -> ignoring($headers)
  772. );
  773. $cache = $this->_createCache(false);
  774. $this->_checking(Expectations::create()
  775. -> never($cache)->clearKey(any(), 'body')
  776. -> ignoring($cache)
  777. );
  778. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  779. $cache
  780. );
  781. $entity->setBody("blah\r\nblah!");
  782. $entity->toString();
  783. $entity->setBody("blah\r\nblah!");
  784. }
  785. public function testBodyIsClearedFromCacheIfNewEncoderSet()
  786. {
  787. $headers = $this->_createHeaderSet(array(), false);
  788. $this->_checking(Expectations::create()
  789. -> ignoring($headers)->toString() -> returns(
  790. "Content-Type: text/plain; charset=utf-8\r\n"
  791. )
  792. -> ignoring($headers)
  793. );
  794. $cache = $this->_createCache(false);
  795. $this->_checking(Expectations::create()
  796. -> one($cache)->clearKey(any(), 'body')
  797. -> ignoring($cache)
  798. );
  799. $otherEncoder = $this->_createEncoder();
  800. $entity = $this->_createEntity($headers, $this->_createEncoder(),
  801. $cache
  802. );
  803. $entity->setBody("blah\r\nblah!");
  804. $entity->toString();
  805. $entity->setEncoder($otherEncoder);
  806. }
  807. public function testBodyIsReadFromCacheWhenUsingToByteStreamIfPresent()
  808. {
  809. $is = $this->_createInputStream();
  810. $cache = $this->_createCache(false);
  811. $this->_checking(Expectations::create()
  812. -> one($cache)->hasKey(any(), 'body') -> returns(true)
  813. -> one($cache)->exportToByteStream(any(), 'body', $is)
  814. -> ignoring($cache)
  815. );
  816. $entity = $this->_createEntity($this->_createHeaderSet(),
  817. $this->_createEncoder(), $cache
  818. );
  819. $entity->setBody('foo');
  820. $entity->toByteStream($is);
  821. }
  822. public function testBodyIsAddedToCacheWhenUsingToByteStream()
  823. {
  824. $is = $this->_createInputStream();
  825. $cache = $this->_createCache(false);
  826. $this->_checking(Expectations::create()
  827. -> one($cache)->hasKey(any(), 'body') -> returns(false)
  828. //The input stream should be fetched for writing
  829. // Proving that it's actually written to is possible, but extremely
  830. // fragile. Best let the acceptance tests cover this aspect
  831. -> one($cache)->getInputByteStream(any(), 'body')
  832. -> ignoring($cache)
  833. );
  834. $entity = $this->_createEntity($this->_createHeaderSet(),
  835. $this->_createEncoder(), $cache
  836. );
  837. $entity->setBody('foo');
  838. $entity->toByteStream($is);
  839. }
  840. public function testFluidInterface()
  841. {
  842. $entity = $this->_createEntity($this->_createHeaderSet(),
  843. $this->_createEncoder(), $this->_createCache()
  844. );
  845. $this->assertSame($entity,
  846. $entity
  847. ->setContentType('text/plain')
  848. ->setEncoder($this->_createEncoder())
  849. ->setId('foo@bar')
  850. ->setDescription('my description')
  851. ->setMaxLineLength(998)
  852. ->setBody('xx')
  853. ->setBoundary('xyz')
  854. ->setChildren(array())
  855. );
  856. }
  857. // -- Private helpers
  858. abstract protected function _createEntity($headers, $encoder, $cache);
  859. protected function _createChild($level = null, $string = '', $stub = true)
  860. {
  861. $child = $this->_mock('Swift_Mime_MimeEntity');
  862. if (isset($level)) {
  863. $this->_checking(Expectations::create()
  864. -> ignoring($child)->getNestingLevel() -> returns($level)
  865. );
  866. }
  867. $this->_checking(Expectations::create()
  868. -> ignoring($child)->toString() -> returns($string)
  869. );
  870. if ($stub) {
  871. $this->_checking(Expectations::create()
  872. -> ignoring($child)
  873. );
  874. }
  875. return $child;
  876. }
  877. protected function _createEncoder($name = 'quoted-printable', $stub = true)
  878. {
  879. $encoder = $this->_mock('Swift_Mime_ContentEncoder');
  880. $this->_checking(Expectations::create()
  881. -> ignoring($encoder)->getName() -> returns($name)
  882. );
  883. if ($stub) {
  884. $this->_checking(Expectations::create()
  885. -> ignoring($encoder)->encodeString(any(), optional())
  886. -> calls(array($this, 'returnStringFromEncoder'))
  887. -> ignoring($encoder)
  888. );
  889. }
  890. return $encoder;
  891. }
  892. protected function _createCache($stub = true)
  893. {
  894. $cache = $this->_mock('Swift_KeyCache');
  895. if ($stub) {
  896. $this->_checking(Expectations::create()
  897. -> ignoring($cache)
  898. );
  899. }
  900. return $cache;
  901. }
  902. protected function _createHeaderSet($headers = array(), $stub = true)
  903. {
  904. $set = $this->_mock('Swift_Mime_HeaderSet');
  905. foreach ($headers as $key => $header) {
  906. $this->_checking(Expectations::create()
  907. -> ignoring($set)->has($key) -> returns(true)
  908. -> ignoring($set)->get($key) -> returns($header)
  909. );
  910. }
  911. if ($stub) {
  912. $this->_checking(Expectations::create()
  913. -> ignoring($set)->newInstance() -> returns($set)
  914. -> ignoring($set)
  915. );
  916. }
  917. return $set;
  918. }
  919. protected function _createHeader($name, $model = null, $params = array(), $stub = true)
  920. {
  921. $header = $this->_mock('Swift_Mime_ParameterizedHeader');
  922. $this->_checking(Expectations::create()
  923. -> ignoring($header)->getFieldName() -> returns($name)
  924. -> ignoring($header)->getFieldBodyModel() -> returns($model)
  925. );
  926. foreach ($params as $key => $value) {
  927. $this->_checking(Expectations::create()
  928. -> ignoring($header)->getParameter($key) -> returns($value)
  929. );
  930. }
  931. if ($stub) {
  932. $this->_checking(Expectations::create()
  933. -> ignoring($header)
  934. );
  935. }
  936. return $header;
  937. }
  938. protected function _createOutputStream($data = null, $stub = true)
  939. {
  940. $os = $this->_mock('Swift_OutputByteStream');
  941. if (isset($data)) {
  942. $pos = $this->_mockery()->states('position')->startsAs('at beginning');
  943. $this->_checking(Expectations::create()
  944. -> ignoring($os)->read(optional()) -> returns($data)
  945. -> when($pos->isNot('at end')) -> then($pos->is('at end'))
  946. -> ignoring($os)->read(optional()) -> returns(false)
  947. );
  948. if ($stub) {
  949. $this->_checking(Expectations::create()
  950. -> ignoring($os)
  951. );
  952. }
  953. }
  954. return $os;
  955. }
  956. protected function _createInputStream($stub = true)
  957. {
  958. $is = $this->_mock('Swift_InputByteStream');
  959. if ($stub) {
  960. $this->_checking(Expectations::create()
  961. -> ignoring($is)
  962. );
  963. }
  964. return $is;
  965. }
  966. // -- Mock helpers
  967. public function returnStringFromEncoder(Yay_Invocation $invocation)
  968. {
  969. $args = $invocation->getArguments();
  970. return array_shift($args);
  971. }
  972. }