diff options
author | Paulino Michelazzo <paulino@michelazzo.com.br> | 2016-10-18 22:48:23 +0200 |
---|---|---|
committer | Paulino Michelazzo <paulino@michelazzo.com.br> | 2016-10-18 22:48:23 +0200 |
commit | 99731f0bb1f6fd2815eeb9af504ce86df927657b (patch) | |
tree | b080efc608d2bbd52b77a4a0067402007f50c5a8 /tests/Wallabag/ImportBundle | |
parent | 3a3c6b866b52721431bed22426d9abfcd0d2dfe0 (diff) | |
parent | 7180aaed45dce62e40620a9e4b202526ebd6a3bb (diff) | |
download | wallabag-99731f0bb1f6fd2815eeb9af504ce86df927657b.tar.gz wallabag-99731f0bb1f6fd2815eeb9af504ce86df927657b.tar.zst wallabag-99731f0bb1f6fd2815eeb9af504ce86df927657b.zip |
Merge remote-tracking branch 'wallabag/master'
Diffstat (limited to 'tests/Wallabag/ImportBundle')
26 files changed, 3196 insertions, 44 deletions
diff --git a/tests/Wallabag/ImportBundle/Command/ImportCommandTest.php b/tests/Wallabag/ImportBundle/Command/ImportCommandTest.php new file mode 100644 index 00000000..7be1eb18 --- /dev/null +++ b/tests/Wallabag/ImportBundle/Command/ImportCommandTest.php | |||
@@ -0,0 +1,85 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Tests\Wallabag\ImportBundle\Command; | ||
4 | |||
5 | use Symfony\Bundle\FrameworkBundle\Console\Application; | ||
6 | use Symfony\Component\Console\Tester\CommandTester; | ||
7 | use Wallabag\ImportBundle\Command\ImportCommand; | ||
8 | use Tests\Wallabag\CoreBundle\WallabagCoreTestCase; | ||
9 | |||
10 | class ImportCommandTest extends WallabagCoreTestCase | ||
11 | { | ||
12 | /** | ||
13 | * @expectedException Symfony\Component\Console\Exception\RuntimeException | ||
14 | * @expectedExceptionMessage Not enough arguments | ||
15 | */ | ||
16 | public function testRunImportCommandWithoutArguments() | ||
17 | { | ||
18 | $application = new Application($this->getClient()->getKernel()); | ||
19 | $application->add(new ImportCommand()); | ||
20 | |||
21 | $command = $application->find('wallabag:import'); | ||
22 | |||
23 | $tester = new CommandTester($command); | ||
24 | $tester->execute([ | ||
25 | 'command' => $command->getName(), | ||
26 | ]); | ||
27 | } | ||
28 | |||
29 | /** | ||
30 | * @expectedException Symfony\Component\Config\Definition\Exception\Exception | ||
31 | * @expectedExceptionMessage not found | ||
32 | */ | ||
33 | public function testRunImportCommandWithoutFilepath() | ||
34 | { | ||
35 | $application = new Application($this->getClient()->getKernel()); | ||
36 | $application->add(new ImportCommand()); | ||
37 | |||
38 | $command = $application->find('wallabag:import'); | ||
39 | |||
40 | $tester = new CommandTester($command); | ||
41 | $tester->execute([ | ||
42 | 'command' => $command->getName(), | ||
43 | 'userId' => 1, | ||
44 | 'filepath' => 1, | ||
45 | ]); | ||
46 | } | ||
47 | |||
48 | /** | ||
49 | * @expectedException Symfony\Component\Config\Definition\Exception\Exception | ||
50 | * @expectedExceptionMessage User with id | ||
51 | */ | ||
52 | public function testRunImportCommandWithoutUserId() | ||
53 | { | ||
54 | $application = new Application($this->getClient()->getKernel()); | ||
55 | $application->add(new ImportCommand()); | ||
56 | |||
57 | $command = $application->find('wallabag:import'); | ||
58 | |||
59 | $tester = new CommandTester($command); | ||
60 | $tester->execute([ | ||
61 | 'command' => $command->getName(), | ||
62 | 'userId' => 0, | ||
63 | 'filepath' => './', | ||
64 | ]); | ||
65 | } | ||
66 | |||
67 | public function testRunImportCommand() | ||
68 | { | ||
69 | $application = new Application($this->getClient()->getKernel()); | ||
70 | $application->add(new ImportCommand()); | ||
71 | |||
72 | $command = $application->find('wallabag:import'); | ||
73 | |||
74 | $tester = new CommandTester($command); | ||
75 | $tester->execute([ | ||
76 | 'command' => $command->getName(), | ||
77 | 'userId' => 1, | ||
78 | 'filepath' => $application->getKernel()->getContainer()->getParameter('kernel.root_dir').'/../tests/Wallabag/ImportBundle/fixtures/wallabag-v2-read.json', | ||
79 | '--importer' => 'v2', | ||
80 | ]); | ||
81 | |||
82 | $this->assertContains('imported', $tester->getDisplay()); | ||
83 | $this->assertContains('already saved', $tester->getDisplay()); | ||
84 | } | ||
85 | } | ||
diff --git a/tests/Wallabag/ImportBundle/Command/RedisWorkerCommandTest.php b/tests/Wallabag/ImportBundle/Command/RedisWorkerCommandTest.php new file mode 100644 index 00000000..74952847 --- /dev/null +++ b/tests/Wallabag/ImportBundle/Command/RedisWorkerCommandTest.php | |||
@@ -0,0 +1,74 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Tests\Wallabag\ImportBundle\Command; | ||
4 | |||
5 | use Symfony\Bundle\FrameworkBundle\Console\Application; | ||
6 | use Symfony\Component\Console\Tester\CommandTester; | ||
7 | use Wallabag\ImportBundle\Command\RedisWorkerCommand; | ||
8 | use Tests\Wallabag\CoreBundle\WallabagCoreTestCase; | ||
9 | use M6Web\Component\RedisMock\RedisMockFactory; | ||
10 | |||
11 | class RedisWorkerCommandTest extends WallabagCoreTestCase | ||
12 | { | ||
13 | /** | ||
14 | * @expectedException Symfony\Component\Console\Exception\RuntimeException | ||
15 | * @expectedExceptionMessage Not enough arguments (missing: "serviceName") | ||
16 | */ | ||
17 | public function testRunRedisWorkerCommandWithoutArguments() | ||
18 | { | ||
19 | $application = new Application($this->getClient()->getKernel()); | ||
20 | $application->add(new RedisWorkerCommand()); | ||
21 | |||
22 | $command = $application->find('wallabag:import:redis-worker'); | ||
23 | |||
24 | $tester = new CommandTester($command); | ||
25 | $tester->execute([ | ||
26 | 'command' => $command->getName(), | ||
27 | ]); | ||
28 | } | ||
29 | |||
30 | /** | ||
31 | * @expectedException Symfony\Component\Config\Definition\Exception\Exception | ||
32 | * @expectedExceptionMessage No queue or consumer found for service name | ||
33 | */ | ||
34 | public function testRunRedisWorkerCommandWithBadService() | ||
35 | { | ||
36 | $application = new Application($this->getClient()->getKernel()); | ||
37 | $application->add(new RedisWorkerCommand()); | ||
38 | |||
39 | $command = $application->find('wallabag:import:redis-worker'); | ||
40 | |||
41 | $tester = new CommandTester($command); | ||
42 | $tester->execute([ | ||
43 | 'command' => $command->getName(), | ||
44 | 'serviceName' => 'YOMONSERVICE', | ||
45 | ]); | ||
46 | } | ||
47 | |||
48 | public function testRunRedisWorkerCommand() | ||
49 | { | ||
50 | $application = new Application($this->getClient()->getKernel()); | ||
51 | $application->add(new RedisWorkerCommand()); | ||
52 | |||
53 | $factory = new RedisMockFactory(); | ||
54 | $redisMock = $factory->getAdapter('Predis\Client', true); | ||
55 | |||
56 | $application->getKernel()->getContainer()->set('wallabag_core.redis.client', $redisMock); | ||
57 | |||
58 | // put a fake message in the queue so the worker will stop after reading that message | ||
59 | // instead of waiting for others | ||
60 | $redisMock->lpush('wallabag.import.readability', '{}'); | ||
61 | |||
62 | $command = $application->find('wallabag:import:redis-worker'); | ||
63 | |||
64 | $tester = new CommandTester($command); | ||
65 | $tester->execute([ | ||
66 | 'command' => $command->getName(), | ||
67 | 'serviceName' => 'readability', | ||
68 | '--maxIterations' => 1, | ||
69 | ]); | ||
70 | |||
71 | $this->assertContains('Worker started at', $tester->getDisplay()); | ||
72 | $this->assertContains('Waiting for message', $tester->getDisplay()); | ||
73 | } | ||
74 | } | ||
diff --git a/tests/Wallabag/ImportBundle/Consumer/AMQPEntryConsumerTest.php b/tests/Wallabag/ImportBundle/Consumer/AMQPEntryConsumerTest.php new file mode 100644 index 00000000..a3263771 --- /dev/null +++ b/tests/Wallabag/ImportBundle/Consumer/AMQPEntryConsumerTest.php | |||
@@ -0,0 +1,225 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Tests\Wallabag\ImportBundle\Consumer\AMQP; | ||
4 | |||
5 | use Wallabag\ImportBundle\Consumer\AMQPEntryConsumer; | ||
6 | use PhpAmqpLib\Message\AMQPMessage; | ||
7 | use Wallabag\UserBundle\Entity\User; | ||
8 | use Wallabag\CoreBundle\Entity\Entry; | ||
9 | |||
10 | class AMQPEntryConsumerTest extends \PHPUnit_Framework_TestCase | ||
11 | { | ||
12 | public function testMessageOk() | ||
13 | { | ||
14 | $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') | ||
15 | ->disableOriginalConstructor() | ||
16 | ->getMock(); | ||
17 | |||
18 | $em | ||
19 | ->expects($this->once()) | ||
20 | ->method('flush'); | ||
21 | |||
22 | $em | ||
23 | ->expects($this->exactly(2)) | ||
24 | ->method('clear'); | ||
25 | |||
26 | $body = <<<'JSON' | ||
27 | { | ||
28 | "item_id": "1402935436", | ||
29 | "resolved_id": "1402935436", | ||
30 | "given_url": "http://mashable.com/2016/09/04/leslie-jones-back-on-twitter-after-hack/?utm_campaign=Mash-Prod-RSS-Feedburner-All-Partial&utm_cid=Mash-Prod-RSS-Feedburner-All-Partial", | ||
31 | "given_title": "Leslie Jones is back on Twitter and her comeback tweet rules", | ||
32 | "favorite": "0", | ||
33 | "status": "0", | ||
34 | "time_added": "1473020899", | ||
35 | "time_updated": "1473020899", | ||
36 | "time_read": "0", | ||
37 | "time_favorited": "0", | ||
38 | "sort_id": 0, | ||
39 | "resolved_title": "Leslie Jones is back on Twitter and her comeback tweet rules", | ||
40 | "resolved_url": "http://mashable.com/2016/09/04/leslie-jones-back-on-twitter-after-hack/?utm_campaign=Mash-Prod-RSS-Feedburner-All-Partial&utm_cid=Mash-Prod-RSS-Feedburner-All-Partial", | ||
41 | "excerpt": "Leslie Jones is back to communicating with her adoring public on Twitter after cowardly hacker-trolls drove her away, probably to compensate for their own failings. It all started with a mic drop ...", | ||
42 | "is_article": "1", | ||
43 | "is_index": "0", | ||
44 | "has_video": "0", | ||
45 | "has_image": "1", | ||
46 | "word_count": "200", | ||
47 | "tags": { | ||
48 | "ifttt": { | ||
49 | "item_id": "1402935436", | ||
50 | "tag": "ifttt" | ||
51 | }, | ||
52 | "mashable": { | ||
53 | "item_id": "1402935436", | ||
54 | "tag": "mashable" | ||
55 | } | ||
56 | }, | ||
57 | "authors": { | ||
58 | "2484273": { | ||
59 | "item_id": "1402935436", | ||
60 | "author_id": "2484273", | ||
61 | "name": "Adam Rosenberg", | ||
62 | "url": "http://mashable.com/author/adam-rosenberg/" | ||
63 | } | ||
64 | }, | ||
65 | "image": { | ||
66 | "item_id": "1402935436", | ||
67 | "src": "http://i.amz.mshcdn.com/i-V5cS6_sDqFABaVR0hVSBJqG_w=/950x534/https%3A%2F%2Fblueprint-api-production.s3.amazonaws.com%2Fuploads%2Fcard%2Fimage%2F199899%2Fleslie_jones_war_dogs.jpg", | ||
68 | "width": "0", | ||
69 | "height": "0" | ||
70 | }, | ||
71 | "images": { | ||
72 | "1": { | ||
73 | "item_id": "1402935436", | ||
74 | "image_id": "1", | ||
75 | "src": "http://i.amz.mshcdn.com/i-V5cS6_sDqFABaVR0hVSBJqG_w=/950x534/https%3A%2F%2Fblueprint-api-production.s3.amazonaws.com%2Fuploads%2Fcard%2Fimage%2F199899%2Fleslie_jones_war_dogs.jpg", | ||
76 | "width": "0", | ||
77 | "height": "0", | ||
78 | "credit": "Image: Steve Eichner/NameFace/Sipa USA", | ||
79 | "caption": "" | ||
80 | } | ||
81 | }, | ||
82 | "userId": 1 | ||
83 | } | ||
84 | JSON; | ||
85 | |||
86 | $user = new User(); | ||
87 | $entry = new Entry($user); | ||
88 | |||
89 | $userRepository = $this->getMockBuilder('Wallabag\UserBundle\Repository\UserRepository') | ||
90 | ->disableOriginalConstructor() | ||
91 | ->getMock(); | ||
92 | |||
93 | $userRepository | ||
94 | ->expects($this->once()) | ||
95 | ->method('find') | ||
96 | // userId from the body json above | ||
97 | ->with(1) | ||
98 | ->willReturn($user); | ||
99 | |||
100 | $import = $this->getMockBuilder('Wallabag\ImportBundle\Import\AbstractImport') | ||
101 | ->disableOriginalConstructor() | ||
102 | ->getMock(); | ||
103 | |||
104 | $import | ||
105 | ->expects($this->once()) | ||
106 | ->method('setUser') | ||
107 | ->with($user); | ||
108 | |||
109 | $import | ||
110 | ->expects($this->once()) | ||
111 | ->method('parseEntry') | ||
112 | ->with(json_decode($body, true)) | ||
113 | ->willReturn($entry); | ||
114 | |||
115 | $consumer = new AMQPEntryConsumer( | ||
116 | $em, | ||
117 | $userRepository, | ||
118 | $import | ||
119 | ); | ||
120 | |||
121 | $message = new AMQPMessage($body); | ||
122 | |||
123 | $consumer->execute($message); | ||
124 | } | ||
125 | |||
126 | public function testMessageWithBadUser() | ||
127 | { | ||
128 | $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') | ||
129 | ->disableOriginalConstructor() | ||
130 | ->getMock(); | ||
131 | |||
132 | $em | ||
133 | ->expects($this->never()) | ||
134 | ->method('flush'); | ||
135 | |||
136 | $em | ||
137 | ->expects($this->never()) | ||
138 | ->method('clear'); | ||
139 | |||
140 | $body = '{ "userId": 123 }'; | ||
141 | |||
142 | $user = new User(); | ||
143 | $entry = new Entry($user); | ||
144 | |||
145 | $userRepository = $this->getMockBuilder('Wallabag\UserBundle\Repository\UserRepository') | ||
146 | ->disableOriginalConstructor() | ||
147 | ->getMock(); | ||
148 | |||
149 | $userRepository | ||
150 | ->expects($this->once()) | ||
151 | ->method('find') | ||
152 | // userId from the body json above | ||
153 | ->with(123) | ||
154 | ->willReturn(null); | ||
155 | |||
156 | $import = $this->getMockBuilder('Wallabag\ImportBundle\Import\AbstractImport') | ||
157 | ->disableOriginalConstructor() | ||
158 | ->getMock(); | ||
159 | |||
160 | $consumer = new AMQPEntryConsumer( | ||
161 | $em, | ||
162 | $userRepository, | ||
163 | $import | ||
164 | ); | ||
165 | |||
166 | $message = new AMQPMessage($body); | ||
167 | |||
168 | $consumer->execute($message); | ||
169 | } | ||
170 | |||
171 | public function testMessageWithEntryProcessed() | ||
172 | { | ||
173 | $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') | ||
174 | ->disableOriginalConstructor() | ||
175 | ->getMock(); | ||
176 | |||
177 | $em | ||
178 | ->expects($this->never()) | ||
179 | ->method('flush'); | ||
180 | |||
181 | $em | ||
182 | ->expects($this->never()) | ||
183 | ->method('clear'); | ||
184 | |||
185 | $body = '{ "userId": 123 }'; | ||
186 | |||
187 | $user = new User(); | ||
188 | |||
189 | $userRepository = $this->getMockBuilder('Wallabag\UserBundle\Repository\UserRepository') | ||
190 | ->disableOriginalConstructor() | ||
191 | ->getMock(); | ||
192 | |||
193 | $userRepository | ||
194 | ->expects($this->once()) | ||
195 | ->method('find') | ||
196 | // userId from the body json above | ||
197 | ->with(123) | ||
198 | ->willReturn($user); | ||
199 | |||
200 | $import = $this->getMockBuilder('Wallabag\ImportBundle\Import\AbstractImport') | ||
201 | ->disableOriginalConstructor() | ||
202 | ->getMock(); | ||
203 | |||
204 | $import | ||
205 | ->expects($this->once()) | ||
206 | ->method('setUser') | ||
207 | ->with($user); | ||
208 | |||
209 | $import | ||
210 | ->expects($this->once()) | ||
211 | ->method('parseEntry') | ||
212 | ->with(json_decode($body, true)) | ||
213 | ->willReturn(null); | ||
214 | |||
215 | $consumer = new AMQPEntryConsumer( | ||
216 | $em, | ||
217 | $userRepository, | ||
218 | $import | ||
219 | ); | ||
220 | |||
221 | $message = new AMQPMessage($body); | ||
222 | |||
223 | $consumer->execute($message); | ||
224 | } | ||
225 | } | ||
diff --git a/tests/Wallabag/ImportBundle/Consumer/RedisEntryConsumerTest.php b/tests/Wallabag/ImportBundle/Consumer/RedisEntryConsumerTest.php new file mode 100644 index 00000000..5e8ee41d --- /dev/null +++ b/tests/Wallabag/ImportBundle/Consumer/RedisEntryConsumerTest.php | |||
@@ -0,0 +1,225 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Tests\Wallabag\ImportBundle\Consumer\AMQP; | ||
4 | |||
5 | use Wallabag\ImportBundle\Consumer\RedisEntryConsumer; | ||
6 | use Wallabag\UserBundle\Entity\User; | ||
7 | use Wallabag\CoreBundle\Entity\Entry; | ||
8 | |||
9 | class RedisEntryConsumerTest extends \PHPUnit_Framework_TestCase | ||
10 | { | ||
11 | public function testMessageOk() | ||
12 | { | ||
13 | $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') | ||
14 | ->disableOriginalConstructor() | ||
15 | ->getMock(); | ||
16 | |||
17 | $em | ||
18 | ->expects($this->once()) | ||
19 | ->method('flush'); | ||
20 | |||
21 | $em | ||
22 | ->expects($this->exactly(2)) | ||
23 | ->method('clear'); | ||
24 | |||
25 | $body = <<<'JSON' | ||
26 | { | ||
27 | "item_id": "1402935436", | ||
28 | "resolved_id": "1402935436", | ||
29 | "given_url": "http://mashable.com/2016/09/04/leslie-jones-back-on-twitter-after-hack/?utm_campaign=Mash-Prod-RSS-Feedburner-All-Partial&utm_cid=Mash-Prod-RSS-Feedburner-All-Partial", | ||
30 | "given_title": "Leslie Jones is back on Twitter and her comeback tweet rules", | ||
31 | "favorite": "0", | ||
32 | "status": "0", | ||
33 | "time_added": "1473020899", | ||
34 | "time_updated": "1473020899", | ||
35 | "time_read": "0", | ||
36 | "time_favorited": "0", | ||
37 | "sort_id": 0, | ||
38 | "resolved_title": "Leslie Jones is back on Twitter and her comeback tweet rules", | ||
39 | "resolved_url": "http://mashable.com/2016/09/04/leslie-jones-back-on-twitter-after-hack/?utm_campaign=Mash-Prod-RSS-Feedburner-All-Partial&utm_cid=Mash-Prod-RSS-Feedburner-All-Partial", | ||
40 | "excerpt": "Leslie Jones is back to communicating with her adoring public on Twitter after cowardly hacker-trolls drove her away, probably to compensate for their own failings. It all started with a mic drop ...", | ||
41 | "is_article": "1", | ||
42 | "is_index": "0", | ||
43 | "has_video": "0", | ||
44 | "has_image": "1", | ||
45 | "word_count": "200", | ||
46 | "tags": { | ||
47 | "ifttt": { | ||
48 | "item_id": "1402935436", | ||
49 | "tag": "ifttt" | ||
50 | }, | ||
51 | "mashable": { | ||
52 | "item_id": "1402935436", | ||
53 | "tag": "mashable" | ||
54 | } | ||
55 | }, | ||
56 | "authors": { | ||
57 | "2484273": { | ||
58 | "item_id": "1402935436", | ||
59 | "author_id": "2484273", | ||
60 | "name": "Adam Rosenberg", | ||
61 | "url": "http://mashable.com/author/adam-rosenberg/" | ||
62 | } | ||
63 | }, | ||
64 | "image": { | ||
65 | "item_id": "1402935436", | ||
66 | "src": "http://i.amz.mshcdn.com/i-V5cS6_sDqFABaVR0hVSBJqG_w=/950x534/https%3A%2F%2Fblueprint-api-production.s3.amazonaws.com%2Fuploads%2Fcard%2Fimage%2F199899%2Fleslie_jones_war_dogs.jpg", | ||
67 | "width": "0", | ||
68 | "height": "0" | ||
69 | }, | ||
70 | "images": { | ||
71 | "1": { | ||
72 | "item_id": "1402935436", | ||
73 | "image_id": "1", | ||
74 | "src": "http://i.amz.mshcdn.com/i-V5cS6_sDqFABaVR0hVSBJqG_w=/950x534/https%3A%2F%2Fblueprint-api-production.s3.amazonaws.com%2Fuploads%2Fcard%2Fimage%2F199899%2Fleslie_jones_war_dogs.jpg", | ||
75 | "width": "0", | ||
76 | "height": "0", | ||
77 | "credit": "Image: Steve Eichner/NameFace/Sipa USA", | ||
78 | "caption": "" | ||
79 | } | ||
80 | }, | ||
81 | "userId": 1 | ||
82 | } | ||
83 | JSON; | ||
84 | |||
85 | $user = new User(); | ||
86 | $entry = new Entry($user); | ||
87 | |||
88 | $userRepository = $this->getMockBuilder('Wallabag\UserBundle\Repository\UserRepository') | ||
89 | ->disableOriginalConstructor() | ||
90 | ->getMock(); | ||
91 | |||
92 | $userRepository | ||
93 | ->expects($this->once()) | ||
94 | ->method('find') | ||
95 | // userId from the body json above | ||
96 | ->with(1) | ||
97 | ->willReturn($user); | ||
98 | |||
99 | $import = $this->getMockBuilder('Wallabag\ImportBundle\Import\AbstractImport') | ||
100 | ->disableOriginalConstructor() | ||
101 | ->getMock(); | ||
102 | |||
103 | $import | ||
104 | ->expects($this->once()) | ||
105 | ->method('setUser') | ||
106 | ->with($user); | ||
107 | |||
108 | $import | ||
109 | ->expects($this->once()) | ||
110 | ->method('parseEntry') | ||
111 | ->with(json_decode($body, true)) | ||
112 | ->willReturn($entry); | ||
113 | |||
114 | $consumer = new RedisEntryConsumer( | ||
115 | $em, | ||
116 | $userRepository, | ||
117 | $import | ||
118 | ); | ||
119 | |||
120 | $res = $consumer->manage($body); | ||
121 | |||
122 | $this->assertTrue($res); | ||
123 | } | ||
124 | |||
125 | public function testMessageWithBadUser() | ||
126 | { | ||
127 | $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') | ||
128 | ->disableOriginalConstructor() | ||
129 | ->getMock(); | ||
130 | |||
131 | $em | ||
132 | ->expects($this->never()) | ||
133 | ->method('flush'); | ||
134 | |||
135 | $em | ||
136 | ->expects($this->never()) | ||
137 | ->method('clear'); | ||
138 | |||
139 | $body = '{ "userId": 123 }'; | ||
140 | |||
141 | $user = new User(); | ||
142 | $entry = new Entry($user); | ||
143 | |||
144 | $userRepository = $this->getMockBuilder('Wallabag\UserBundle\Repository\UserRepository') | ||
145 | ->disableOriginalConstructor() | ||
146 | ->getMock(); | ||
147 | |||
148 | $userRepository | ||
149 | ->expects($this->once()) | ||
150 | ->method('find') | ||
151 | // userId from the body json above | ||
152 | ->with(123) | ||
153 | ->willReturn(null); | ||
154 | |||
155 | $import = $this->getMockBuilder('Wallabag\ImportBundle\Import\AbstractImport') | ||
156 | ->disableOriginalConstructor() | ||
157 | ->getMock(); | ||
158 | |||
159 | $consumer = new RedisEntryConsumer( | ||
160 | $em, | ||
161 | $userRepository, | ||
162 | $import | ||
163 | ); | ||
164 | |||
165 | $res = $consumer->manage($body); | ||
166 | |||
167 | $this->assertFalse($res); | ||
168 | } | ||
169 | |||
170 | public function testMessageWithEntryProcessed() | ||
171 | { | ||
172 | $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') | ||
173 | ->disableOriginalConstructor() | ||
174 | ->getMock(); | ||
175 | |||
176 | $em | ||
177 | ->expects($this->never()) | ||
178 | ->method('flush'); | ||
179 | |||
180 | $em | ||
181 | ->expects($this->never()) | ||
182 | ->method('clear'); | ||
183 | |||
184 | $body = '{ "userId": 123 }'; | ||
185 | |||
186 | $user = new User(); | ||
187 | |||
188 | $userRepository = $this->getMockBuilder('Wallabag\UserBundle\Repository\UserRepository') | ||
189 | ->disableOriginalConstructor() | ||
190 | ->getMock(); | ||
191 | |||
192 | $userRepository | ||
193 | ->expects($this->once()) | ||
194 | ->method('find') | ||
195 | // userId from the body json above | ||
196 | ->with(123) | ||
197 | ->willReturn($user); | ||
198 | |||
199 | $import = $this->getMockBuilder('Wallabag\ImportBundle\Import\AbstractImport') | ||
200 | ->disableOriginalConstructor() | ||
201 | ->getMock(); | ||
202 | |||
203 | $import | ||
204 | ->expects($this->once()) | ||
205 | ->method('setUser') | ||
206 | ->with($user); | ||
207 | |||
208 | $import | ||
209 | ->expects($this->once()) | ||
210 | ->method('parseEntry') | ||
211 | ->with(json_decode($body, true)) | ||
212 | ->willReturn(null); | ||
213 | |||
214 | $consumer = new RedisEntryConsumer( | ||
215 | $em, | ||
216 | $userRepository, | ||
217 | $import | ||
218 | ); | ||
219 | |||
220 | $res = $consumer->manage($body); | ||
221 | |||
222 | $this->assertFalse($res); | ||
223 | $this->assertFalse($consumer->isStopJob($body)); | ||
224 | } | ||
225 | } | ||
diff --git a/tests/Wallabag/ImportBundle/Controller/ChromeControllerTest.php b/tests/Wallabag/ImportBundle/Controller/ChromeControllerTest.php new file mode 100644 index 00000000..c0417bbe --- /dev/null +++ b/tests/Wallabag/ImportBundle/Controller/ChromeControllerTest.php | |||
@@ -0,0 +1,153 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Tests\Wallabag\ImportBundle\Controller; | ||
4 | |||
5 | use Tests\Wallabag\CoreBundle\WallabagCoreTestCase; | ||
6 | use Symfony\Component\HttpFoundation\File\UploadedFile; | ||
7 | |||
8 | class ChromeControllerTest extends WallabagCoreTestCase | ||
9 | { | ||
10 | public function testImportChrome() | ||
11 | { | ||
12 | $this->logInAs('admin'); | ||
13 | $client = $this->getClient(); | ||
14 | |||
15 | $crawler = $client->request('GET', '/import/chrome'); | ||
16 | |||
17 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
18 | $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count()); | ||
19 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | ||
20 | } | ||
21 | |||
22 | public function testImportChromeWithRabbitEnabled() | ||
23 | { | ||
24 | $this->logInAs('admin'); | ||
25 | $client = $this->getClient(); | ||
26 | |||
27 | $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 1); | ||
28 | |||
29 | $crawler = $client->request('GET', '/import/chrome'); | ||
30 | |||
31 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
32 | $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count()); | ||
33 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | ||
34 | |||
35 | $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 0); | ||
36 | } | ||
37 | |||
38 | public function testImportChromeBadFile() | ||
39 | { | ||
40 | $this->logInAs('admin'); | ||
41 | $client = $this->getClient(); | ||
42 | |||
43 | $crawler = $client->request('GET', '/import/chrome'); | ||
44 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
45 | |||
46 | $data = [ | ||
47 | 'upload_import_file[file]' => '', | ||
48 | ]; | ||
49 | |||
50 | $client->submit($form, $data); | ||
51 | |||
52 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
53 | } | ||
54 | |||
55 | public function testImportChromeWithRedisEnabled() | ||
56 | { | ||
57 | $this->checkRedis(); | ||
58 | $this->logInAs('admin'); | ||
59 | $client = $this->getClient(); | ||
60 | $client->getContainer()->get('craue_config')->set('import_with_redis', 1); | ||
61 | |||
62 | $crawler = $client->request('GET', '/import/chrome'); | ||
63 | |||
64 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
65 | $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count()); | ||
66 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | ||
67 | |||
68 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
69 | |||
70 | $file = new UploadedFile(__DIR__.'/../fixtures/chrome-bookmarks', 'Bookmarks'); | ||
71 | |||
72 | $data = [ | ||
73 | 'upload_import_file[file]' => $file, | ||
74 | ]; | ||
75 | |||
76 | $client->submit($form, $data); | ||
77 | |||
78 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
79 | |||
80 | $crawler = $client->followRedirect(); | ||
81 | |||
82 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); | ||
83 | $this->assertContains('flashes.import.notice.summary', $body[0]); | ||
84 | |||
85 | $this->assertNotEmpty($client->getContainer()->get('wallabag_core.redis.client')->lpop('wallabag.import.chrome')); | ||
86 | |||
87 | $client->getContainer()->get('craue_config')->set('import_with_redis', 0); | ||
88 | } | ||
89 | |||
90 | public function testImportWallabagWithChromeFile() | ||
91 | { | ||
92 | $this->logInAs('admin'); | ||
93 | $client = $this->getClient(); | ||
94 | |||
95 | $crawler = $client->request('GET', '/import/chrome'); | ||
96 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
97 | |||
98 | $file = new UploadedFile(__DIR__.'/../fixtures/chrome-bookmarks', 'Bookmarks'); | ||
99 | |||
100 | $data = [ | ||
101 | 'upload_import_file[file]' => $file, | ||
102 | ]; | ||
103 | |||
104 | $client->submit($form, $data); | ||
105 | |||
106 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
107 | |||
108 | $crawler = $client->followRedirect(); | ||
109 | |||
110 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); | ||
111 | $this->assertContains('flashes.import.notice.summary', $body[0]); | ||
112 | |||
113 | $content = $client->getContainer() | ||
114 | ->get('doctrine.orm.entity_manager') | ||
115 | ->getRepository('WallabagCoreBundle:Entry') | ||
116 | ->findByUrlAndUserId( | ||
117 | 'http://www.usinenouvelle.com/article/la-multiplication-des-chefs-de-projet-est-une-catastrophe-manageriale-majeure-affirme-le-sociologue-francois-dupuy.N307730', | ||
118 | $this->getLoggedInUserId() | ||
119 | ); | ||
120 | |||
121 | $this->assertNotEmpty($content->getPreviewPicture()); | ||
122 | $this->assertNotEmpty($content->getLanguage()); | ||
123 | $this->assertEquals(0, count($content->getTags())); | ||
124 | |||
125 | $createdAt = $content->getCreatedAt(); | ||
126 | $this->assertEquals('2011', $createdAt->format('Y')); | ||
127 | $this->assertEquals('07', $createdAt->format('m')); | ||
128 | } | ||
129 | |||
130 | public function testImportWallabagWithEmptyFile() | ||
131 | { | ||
132 | $this->logInAs('admin'); | ||
133 | $client = $this->getClient(); | ||
134 | |||
135 | $crawler = $client->request('GET', '/import/chrome'); | ||
136 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
137 | |||
138 | $file = new UploadedFile(__DIR__.'/../fixtures/test.txt', 'test.txt'); | ||
139 | |||
140 | $data = [ | ||
141 | 'upload_import_file[file]' => $file, | ||
142 | ]; | ||
143 | |||
144 | $client->submit($form, $data); | ||
145 | |||
146 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
147 | |||
148 | $crawler = $client->followRedirect(); | ||
149 | |||
150 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); | ||
151 | $this->assertContains('flashes.import.notice.failed', $body[0]); | ||
152 | } | ||
153 | } | ||
diff --git a/tests/Wallabag/ImportBundle/Controller/FirefoxControllerTest.php b/tests/Wallabag/ImportBundle/Controller/FirefoxControllerTest.php new file mode 100644 index 00000000..6154ae8d --- /dev/null +++ b/tests/Wallabag/ImportBundle/Controller/FirefoxControllerTest.php | |||
@@ -0,0 +1,166 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Tests\Wallabag\ImportBundle\Controller; | ||
4 | |||
5 | use Tests\Wallabag\CoreBundle\WallabagCoreTestCase; | ||
6 | use Symfony\Component\HttpFoundation\File\UploadedFile; | ||
7 | |||
8 | class FirefoxControllerTest extends WallabagCoreTestCase | ||
9 | { | ||
10 | public function testImportFirefox() | ||
11 | { | ||
12 | $this->logInAs('admin'); | ||
13 | $client = $this->getClient(); | ||
14 | |||
15 | $crawler = $client->request('GET', '/import/firefox'); | ||
16 | |||
17 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
18 | $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count()); | ||
19 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | ||
20 | } | ||
21 | |||
22 | public function testImportFirefoxWithRabbitEnabled() | ||
23 | { | ||
24 | $this->logInAs('admin'); | ||
25 | $client = $this->getClient(); | ||
26 | |||
27 | $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 1); | ||
28 | |||
29 | $crawler = $client->request('GET', '/import/firefox'); | ||
30 | |||
31 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
32 | $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count()); | ||
33 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | ||
34 | |||
35 | $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 0); | ||
36 | } | ||
37 | |||
38 | public function testImportFirefoxBadFile() | ||
39 | { | ||
40 | $this->logInAs('admin'); | ||
41 | $client = $this->getClient(); | ||
42 | |||
43 | $crawler = $client->request('GET', '/import/firefox'); | ||
44 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
45 | |||
46 | $data = [ | ||
47 | 'upload_import_file[file]' => '', | ||
48 | ]; | ||
49 | |||
50 | $client->submit($form, $data); | ||
51 | |||
52 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
53 | } | ||
54 | |||
55 | public function testImportFirefoxWithRedisEnabled() | ||
56 | { | ||
57 | $this->checkRedis(); | ||
58 | $this->logInAs('admin'); | ||
59 | $client = $this->getClient(); | ||
60 | $client->getContainer()->get('craue_config')->set('import_with_redis', 1); | ||
61 | |||
62 | $crawler = $client->request('GET', '/import/firefox'); | ||
63 | |||
64 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
65 | $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count()); | ||
66 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | ||
67 | |||
68 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
69 | |||
70 | $file = new UploadedFile(__DIR__.'/../fixtures/firefox-bookmarks.json', 'Bookmarks'); | ||
71 | |||
72 | $data = [ | ||
73 | 'upload_import_file[file]' => $file, | ||
74 | ]; | ||
75 | |||
76 | $client->submit($form, $data); | ||
77 | |||
78 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
79 | |||
80 | $crawler = $client->followRedirect(); | ||
81 | |||
82 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); | ||
83 | $this->assertContains('flashes.import.notice.summary', $body[0]); | ||
84 | |||
85 | $this->assertNotEmpty($client->getContainer()->get('wallabag_core.redis.client')->lpop('wallabag.import.firefox')); | ||
86 | |||
87 | $client->getContainer()->get('craue_config')->set('import_with_redis', 0); | ||
88 | } | ||
89 | |||
90 | public function testImportWallabagWithFirefoxFile() | ||
91 | { | ||
92 | $this->logInAs('admin'); | ||
93 | $client = $this->getClient(); | ||
94 | |||
95 | $crawler = $client->request('GET', '/import/firefox'); | ||
96 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
97 | |||
98 | $file = new UploadedFile(__DIR__.'/../fixtures/firefox-bookmarks.json', 'Bookmarks'); | ||
99 | |||
100 | $data = [ | ||
101 | 'upload_import_file[file]' => $file, | ||
102 | ]; | ||
103 | |||
104 | $client->submit($form, $data); | ||
105 | |||
106 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
107 | |||
108 | $crawler = $client->followRedirect(); | ||
109 | |||
110 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); | ||
111 | $this->assertContains('flashes.import.notice.summary', $body[0]); | ||
112 | |||
113 | $content = $client->getContainer() | ||
114 | ->get('doctrine.orm.entity_manager') | ||
115 | ->getRepository('WallabagCoreBundle:Entry') | ||
116 | ->findByUrlAndUserId( | ||
117 | 'http://lexpansion.lexpress.fr/high-tech/orange-offre-un-meilleur-reseau-mobile-que-bouygues-et-sfr-free-derriere_1811554.html', | ||
118 | $this->getLoggedInUserId() | ||
119 | ); | ||
120 | |||
121 | $this->assertNotEmpty($content->getMimetype()); | ||
122 | $this->assertNotEmpty($content->getPreviewPicture()); | ||
123 | $this->assertNotEmpty($content->getLanguage()); | ||
124 | $this->assertEquals(2, count($content->getTags())); | ||
125 | |||
126 | $content = $client->getContainer() | ||
127 | ->get('doctrine.orm.entity_manager') | ||
128 | ->getRepository('WallabagCoreBundle:Entry') | ||
129 | ->findByUrlAndUserId( | ||
130 | 'http://stackoverflow.com/questions/15017163/parser-for-exported-bookmarks-html-file-of-google-chrome-and-mozilla-in-java', | ||
131 | $this->getLoggedInUserId() | ||
132 | ); | ||
133 | |||
134 | $this->assertNotEmpty($content->getMimetype()); | ||
135 | $this->assertNotEmpty($content->getPreviewPicture()); | ||
136 | $this->assertEmpty($content->getLanguage()); | ||
137 | |||
138 | $createdAt = $content->getCreatedAt(); | ||
139 | $this->assertEquals('2013', $createdAt->format('Y')); | ||
140 | $this->assertEquals('12', $createdAt->format('m')); | ||
141 | } | ||
142 | |||
143 | public function testImportWallabagWithEmptyFile() | ||
144 | { | ||
145 | $this->logInAs('admin'); | ||
146 | $client = $this->getClient(); | ||
147 | |||
148 | $crawler = $client->request('GET', '/import/firefox'); | ||
149 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
150 | |||
151 | $file = new UploadedFile(__DIR__.'/../fixtures/test.txt', 'test.txt'); | ||
152 | |||
153 | $data = [ | ||
154 | 'upload_import_file[file]' => $file, | ||
155 | ]; | ||
156 | |||
157 | $client->submit($form, $data); | ||
158 | |||
159 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
160 | |||
161 | $crawler = $client->followRedirect(); | ||
162 | |||
163 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); | ||
164 | $this->assertContains('flashes.import.notice.failed', $body[0]); | ||
165 | } | ||
166 | } | ||
diff --git a/tests/Wallabag/ImportBundle/Controller/ImportControllerTest.php b/tests/Wallabag/ImportBundle/Controller/ImportControllerTest.php index 96b5300b..0bc40bdd 100644 --- a/tests/Wallabag/ImportBundle/Controller/ImportControllerTest.php +++ b/tests/Wallabag/ImportBundle/Controller/ImportControllerTest.php | |||
@@ -24,6 +24,6 @@ class ImportControllerTest extends WallabagCoreTestCase | |||
24 | $crawler = $client->request('GET', '/import/'); | 24 | $crawler = $client->request('GET', '/import/'); |
25 | 25 | ||
26 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | 26 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); |
27 | $this->assertEquals(3, $crawler->filter('blockquote')->count()); | 27 | $this->assertEquals(7, $crawler->filter('blockquote')->count()); |
28 | } | 28 | } |
29 | } | 29 | } |
diff --git a/tests/Wallabag/ImportBundle/Controller/InstapaperControllerTest.php b/tests/Wallabag/ImportBundle/Controller/InstapaperControllerTest.php new file mode 100644 index 00000000..9df08e75 --- /dev/null +++ b/tests/Wallabag/ImportBundle/Controller/InstapaperControllerTest.php | |||
@@ -0,0 +1,196 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Tests\Wallabag\ImportBundle\Controller; | ||
4 | |||
5 | use Tests\Wallabag\CoreBundle\WallabagCoreTestCase; | ||
6 | use Symfony\Component\HttpFoundation\File\UploadedFile; | ||
7 | |||
8 | class InstapaperControllerTest extends WallabagCoreTestCase | ||
9 | { | ||
10 | public function testImportInstapaper() | ||
11 | { | ||
12 | $this->logInAs('admin'); | ||
13 | $client = $this->getClient(); | ||
14 | |||
15 | $crawler = $client->request('GET', '/import/instapaper'); | ||
16 | |||
17 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
18 | $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count()); | ||
19 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | ||
20 | } | ||
21 | |||
22 | public function testImportInstapaperWithRabbitEnabled() | ||
23 | { | ||
24 | $this->logInAs('admin'); | ||
25 | $client = $this->getClient(); | ||
26 | |||
27 | $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 1); | ||
28 | |||
29 | $crawler = $client->request('GET', '/import/instapaper'); | ||
30 | |||
31 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
32 | $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count()); | ||
33 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | ||
34 | |||
35 | $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 0); | ||
36 | } | ||
37 | |||
38 | public function testImportInstapaperBadFile() | ||
39 | { | ||
40 | $this->logInAs('admin'); | ||
41 | $client = $this->getClient(); | ||
42 | |||
43 | $crawler = $client->request('GET', '/import/instapaper'); | ||
44 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
45 | |||
46 | $data = [ | ||
47 | 'upload_import_file[file]' => '', | ||
48 | ]; | ||
49 | |||
50 | $client->submit($form, $data); | ||
51 | |||
52 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
53 | } | ||
54 | |||
55 | public function testImportInstapaperWithRedisEnabled() | ||
56 | { | ||
57 | $this->checkRedis(); | ||
58 | $this->logInAs('admin'); | ||
59 | $client = $this->getClient(); | ||
60 | $client->getContainer()->get('craue_config')->set('import_with_redis', 1); | ||
61 | |||
62 | $crawler = $client->request('GET', '/import/instapaper'); | ||
63 | |||
64 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
65 | $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count()); | ||
66 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | ||
67 | |||
68 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
69 | |||
70 | $file = new UploadedFile(__DIR__.'/../fixtures/instapaper-export.csv', 'instapaper.csv'); | ||
71 | |||
72 | $data = [ | ||
73 | 'upload_import_file[file]' => $file, | ||
74 | ]; | ||
75 | |||
76 | $client->submit($form, $data); | ||
77 | |||
78 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
79 | |||
80 | $crawler = $client->followRedirect(); | ||
81 | |||
82 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); | ||
83 | $this->assertContains('flashes.import.notice.summary', $body[0]); | ||
84 | |||
85 | $this->assertNotEmpty($client->getContainer()->get('wallabag_core.redis.client')->lpop('wallabag.import.instapaper')); | ||
86 | |||
87 | $client->getContainer()->get('craue_config')->set('import_with_redis', 0); | ||
88 | } | ||
89 | |||
90 | public function testImportInstapaperWithFile() | ||
91 | { | ||
92 | $this->logInAs('admin'); | ||
93 | $client = $this->getClient(); | ||
94 | |||
95 | $crawler = $client->request('GET', '/import/instapaper'); | ||
96 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
97 | |||
98 | $file = new UploadedFile(__DIR__.'/../fixtures/instapaper-export.csv', 'instapaper.csv'); | ||
99 | |||
100 | $data = [ | ||
101 | 'upload_import_file[file]' => $file, | ||
102 | ]; | ||
103 | |||
104 | $client->submit($form, $data); | ||
105 | |||
106 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
107 | |||
108 | $crawler = $client->followRedirect(); | ||
109 | |||
110 | $content = $client->getContainer() | ||
111 | ->get('doctrine.orm.entity_manager') | ||
112 | ->getRepository('WallabagCoreBundle:Entry') | ||
113 | ->findByUrlAndUserId( | ||
114 | 'http://www.liberation.fr/societe/2012/12/06/baumettes-un-tour-en-cellule_865551', | ||
115 | $this->getLoggedInUserId() | ||
116 | ); | ||
117 | |||
118 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); | ||
119 | $this->assertContains('flashes.import.notice.summary', $body[0]); | ||
120 | |||
121 | $this->assertNotEmpty($content->getMimetype()); | ||
122 | $this->assertNotEmpty($content->getPreviewPicture()); | ||
123 | $this->assertNotEmpty($content->getLanguage()); | ||
124 | $this->assertEquals(0, count($content->getTags())); | ||
125 | $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt()); | ||
126 | } | ||
127 | |||
128 | public function testImportInstapaperWithFileAndMarkAllAsRead() | ||
129 | { | ||
130 | $this->logInAs('admin'); | ||
131 | $client = $this->getClient(); | ||
132 | |||
133 | $crawler = $client->request('GET', '/import/instapaper'); | ||
134 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
135 | |||
136 | $file = new UploadedFile(__DIR__.'/../fixtures/instapaper-export.csv', 'instapaper-read.csv'); | ||
137 | |||
138 | $data = [ | ||
139 | 'upload_import_file[file]' => $file, | ||
140 | 'upload_import_file[mark_as_read]' => 1, | ||
141 | ]; | ||
142 | |||
143 | $client->submit($form, $data); | ||
144 | |||
145 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
146 | |||
147 | $crawler = $client->followRedirect(); | ||
148 | |||
149 | $content1 = $client->getContainer() | ||
150 | ->get('doctrine.orm.entity_manager') | ||
151 | ->getRepository('WallabagCoreBundle:Entry') | ||
152 | ->findByUrlAndUserId( | ||
153 | 'https://redditblog.com/2016/09/20/amp-and-reactredux/', | ||
154 | $this->getLoggedInUserId() | ||
155 | ); | ||
156 | |||
157 | $this->assertTrue($content1->isArchived()); | ||
158 | |||
159 | $content2 = $client->getContainer() | ||
160 | ->get('doctrine.orm.entity_manager') | ||
161 | ->getRepository('WallabagCoreBundle:Entry') | ||
162 | ->findByUrlAndUserId( | ||
163 | 'https://medium.com/@the_minh/why-foursquare-swarm-is-still-my-favourite-social-network-e38228493e6c', | ||
164 | $this->getLoggedInUserId() | ||
165 | ); | ||
166 | |||
167 | $this->assertTrue($content2->isArchived()); | ||
168 | |||
169 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); | ||
170 | $this->assertContains('flashes.import.notice.summary', $body[0]); | ||
171 | } | ||
172 | |||
173 | public function testImportInstapaperWithEmptyFile() | ||
174 | { | ||
175 | $this->logInAs('admin'); | ||
176 | $client = $this->getClient(); | ||
177 | |||
178 | $crawler = $client->request('GET', '/import/instapaper'); | ||
179 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
180 | |||
181 | $file = new UploadedFile(__DIR__.'/../fixtures/test.txt', 'test.txt'); | ||
182 | |||
183 | $data = [ | ||
184 | 'upload_import_file[file]' => $file, | ||
185 | ]; | ||
186 | |||
187 | $client->submit($form, $data); | ||
188 | |||
189 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
190 | |||
191 | $crawler = $client->followRedirect(); | ||
192 | |||
193 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); | ||
194 | $this->assertContains('flashes.import.notice.failed', $body[0]); | ||
195 | } | ||
196 | } | ||
diff --git a/tests/Wallabag/ImportBundle/Controller/PocketControllerTest.php b/tests/Wallabag/ImportBundle/Controller/PocketControllerTest.php index e0e61df8..7d6a300f 100644 --- a/tests/Wallabag/ImportBundle/Controller/PocketControllerTest.php +++ b/tests/Wallabag/ImportBundle/Controller/PocketControllerTest.php | |||
@@ -17,6 +17,37 @@ class PocketControllerTest extends WallabagCoreTestCase | |||
17 | $this->assertEquals(1, $crawler->filter('button[type=submit]')->count()); | 17 | $this->assertEquals(1, $crawler->filter('button[type=submit]')->count()); |
18 | } | 18 | } |
19 | 19 | ||
20 | public function testImportPocketWithRabbitEnabled() | ||
21 | { | ||
22 | $this->logInAs('admin'); | ||
23 | $client = $this->getClient(); | ||
24 | |||
25 | $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 1); | ||
26 | |||
27 | $crawler = $client->request('GET', '/import/pocket'); | ||
28 | |||
29 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
30 | $this->assertEquals(1, $crawler->filter('button[type=submit]')->count()); | ||
31 | |||
32 | $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 0); | ||
33 | } | ||
34 | |||
35 | public function testImportPocketWithRedisEnabled() | ||
36 | { | ||
37 | $this->checkRedis(); | ||
38 | $this->logInAs('admin'); | ||
39 | $client = $this->getClient(); | ||
40 | |||
41 | $client->getContainer()->get('craue_config')->set('import_with_redis', 1); | ||
42 | |||
43 | $crawler = $client->request('GET', '/import/pocket'); | ||
44 | |||
45 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
46 | $this->assertEquals(1, $crawler->filter('button[type=submit]')->count()); | ||
47 | |||
48 | $client->getContainer()->get('craue_config')->set('import_with_redis', 0); | ||
49 | } | ||
50 | |||
20 | public function testImportPocketAuthBadToken() | 51 | public function testImportPocketAuthBadToken() |
21 | { | 52 | { |
22 | $this->logInAs('admin'); | 53 | $this->logInAs('admin'); |
diff --git a/tests/Wallabag/ImportBundle/Controller/ReadabilityControllerTest.php b/tests/Wallabag/ImportBundle/Controller/ReadabilityControllerTest.php new file mode 100644 index 00000000..916dd297 --- /dev/null +++ b/tests/Wallabag/ImportBundle/Controller/ReadabilityControllerTest.php | |||
@@ -0,0 +1,197 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Tests\Wallabag\ImportBundle\Controller; | ||
4 | |||
5 | use Tests\Wallabag\CoreBundle\WallabagCoreTestCase; | ||
6 | use Symfony\Component\HttpFoundation\File\UploadedFile; | ||
7 | |||
8 | class ReadabilityControllerTest extends WallabagCoreTestCase | ||
9 | { | ||
10 | public function testImportReadability() | ||
11 | { | ||
12 | $this->logInAs('admin'); | ||
13 | $client = $this->getClient(); | ||
14 | |||
15 | $crawler = $client->request('GET', '/import/readability'); | ||
16 | |||
17 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
18 | $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count()); | ||
19 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | ||
20 | } | ||
21 | |||
22 | public function testImportReadabilityWithRabbitEnabled() | ||
23 | { | ||
24 | $this->logInAs('admin'); | ||
25 | $client = $this->getClient(); | ||
26 | |||
27 | $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 1); | ||
28 | |||
29 | $crawler = $client->request('GET', '/import/readability'); | ||
30 | |||
31 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
32 | $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count()); | ||
33 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | ||
34 | |||
35 | $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 0); | ||
36 | } | ||
37 | |||
38 | public function testImportReadabilityBadFile() | ||
39 | { | ||
40 | $this->logInAs('admin'); | ||
41 | $client = $this->getClient(); | ||
42 | |||
43 | $crawler = $client->request('GET', '/import/readability'); | ||
44 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
45 | |||
46 | $data = [ | ||
47 | 'upload_import_file[file]' => '', | ||
48 | ]; | ||
49 | |||
50 | $client->submit($form, $data); | ||
51 | |||
52 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
53 | } | ||
54 | |||
55 | public function testImportReadabilityWithRedisEnabled() | ||
56 | { | ||
57 | $this->checkRedis(); | ||
58 | $this->logInAs('admin'); | ||
59 | $client = $this->getClient(); | ||
60 | $client->getContainer()->get('craue_config')->set('import_with_redis', 1); | ||
61 | |||
62 | $crawler = $client->request('GET', '/import/readability'); | ||
63 | |||
64 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
65 | $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count()); | ||
66 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | ||
67 | |||
68 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
69 | |||
70 | $file = new UploadedFile(__DIR__.'/../fixtures/readability.json', 'readability.json'); | ||
71 | |||
72 | $data = [ | ||
73 | 'upload_import_file[file]' => $file, | ||
74 | ]; | ||
75 | |||
76 | $client->submit($form, $data); | ||
77 | |||
78 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
79 | |||
80 | $crawler = $client->followRedirect(); | ||
81 | |||
82 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); | ||
83 | $this->assertContains('flashes.import.notice.summary', $body[0]); | ||
84 | |||
85 | $this->assertNotEmpty($client->getContainer()->get('wallabag_core.redis.client')->lpop('wallabag.import.readability')); | ||
86 | |||
87 | $client->getContainer()->get('craue_config')->set('import_with_redis', 0); | ||
88 | } | ||
89 | |||
90 | public function testImportReadabilityWithFile() | ||
91 | { | ||
92 | $this->logInAs('admin'); | ||
93 | $client = $this->getClient(); | ||
94 | |||
95 | $crawler = $client->request('GET', '/import/readability'); | ||
96 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
97 | |||
98 | $file = new UploadedFile(__DIR__.'/../fixtures/readability.json', 'readability.json'); | ||
99 | |||
100 | $data = [ | ||
101 | 'upload_import_file[file]' => $file, | ||
102 | ]; | ||
103 | |||
104 | $client->submit($form, $data); | ||
105 | |||
106 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
107 | |||
108 | $crawler = $client->followRedirect(); | ||
109 | |||
110 | $content = $client->getContainer() | ||
111 | ->get('doctrine.orm.entity_manager') | ||
112 | ->getRepository('WallabagCoreBundle:Entry') | ||
113 | ->findByUrlAndUserId( | ||
114 | 'https://venngage.com/blog/hashtags-are-worthless/', | ||
115 | $this->getLoggedInUserId() | ||
116 | ); | ||
117 | |||
118 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); | ||
119 | $this->assertContains('flashes.import.notice.summary', $body[0]); | ||
120 | |||
121 | $this->assertNotEmpty($content->getMimetype()); | ||
122 | $this->assertNotEmpty($content->getPreviewPicture()); | ||
123 | $this->assertNotEmpty($content->getLanguage()); | ||
124 | $this->assertEquals(0, count($content->getTags())); | ||
125 | $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt()); | ||
126 | $this->assertEquals('2016-08-25', $content->getCreatedAt()->format('Y-m-d')); | ||
127 | } | ||
128 | |||
129 | public function testImportReadabilityWithFileAndMarkAllAsRead() | ||
130 | { | ||
131 | $this->logInAs('admin'); | ||
132 | $client = $this->getClient(); | ||
133 | |||
134 | $crawler = $client->request('GET', '/import/readability'); | ||
135 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
136 | |||
137 | $file = new UploadedFile(__DIR__.'/../fixtures/readability-read.json', 'readability-read.json'); | ||
138 | |||
139 | $data = [ | ||
140 | 'upload_import_file[file]' => $file, | ||
141 | 'upload_import_file[mark_as_read]' => 1, | ||
142 | ]; | ||
143 | |||
144 | $client->submit($form, $data); | ||
145 | |||
146 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
147 | |||
148 | $crawler = $client->followRedirect(); | ||
149 | |||
150 | $content1 = $client->getContainer() | ||
151 | ->get('doctrine.orm.entity_manager') | ||
152 | ->getRepository('WallabagCoreBundle:Entry') | ||
153 | ->findByUrlAndUserId( | ||
154 | 'https://blog.travis-ci.com/2016-07-28-what-we-learned-from-analyzing-2-million-travis-builds/', | ||
155 | $this->getLoggedInUserId() | ||
156 | ); | ||
157 | |||
158 | $this->assertTrue($content1->isArchived()); | ||
159 | |||
160 | $content2 = $client->getContainer() | ||
161 | ->get('doctrine.orm.entity_manager') | ||
162 | ->getRepository('WallabagCoreBundle:Entry') | ||
163 | ->findByUrlAndUserId( | ||
164 | 'https://facebook.github.io/graphql/', | ||
165 | $this->getLoggedInUserId() | ||
166 | ); | ||
167 | |||
168 | $this->assertTrue($content2->isArchived()); | ||
169 | |||
170 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); | ||
171 | $this->assertContains('flashes.import.notice.summary', $body[0]); | ||
172 | } | ||
173 | |||
174 | public function testImportReadabilityWithEmptyFile() | ||
175 | { | ||
176 | $this->logInAs('admin'); | ||
177 | $client = $this->getClient(); | ||
178 | |||
179 | $crawler = $client->request('GET', '/import/readability'); | ||
180 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
181 | |||
182 | $file = new UploadedFile(__DIR__.'/../fixtures/test.txt', 'test.txt'); | ||
183 | |||
184 | $data = [ | ||
185 | 'upload_import_file[file]' => $file, | ||
186 | ]; | ||
187 | |||
188 | $client->submit($form, $data); | ||
189 | |||
190 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
191 | |||
192 | $crawler = $client->followRedirect(); | ||
193 | |||
194 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); | ||
195 | $this->assertContains('flashes.import.notice.failed', $body[0]); | ||
196 | } | ||
197 | } | ||
diff --git a/tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php b/tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php index c1025b41..3497c4b8 100644 --- a/tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php +++ b/tests/Wallabag/ImportBundle/Controller/WallabagV1ControllerTest.php | |||
@@ -19,6 +19,75 @@ class WallabagV1ControllerTest extends WallabagCoreTestCase | |||
19 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | 19 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); |
20 | } | 20 | } |
21 | 21 | ||
22 | public function testImportWallabagWithRabbitEnabled() | ||
23 | { | ||
24 | $this->logInAs('admin'); | ||
25 | $client = $this->getClient(); | ||
26 | |||
27 | $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 1); | ||
28 | |||
29 | $crawler = $client->request('GET', '/import/wallabag-v1'); | ||
30 | |||
31 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
32 | $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count()); | ||
33 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | ||
34 | |||
35 | $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 0); | ||
36 | } | ||
37 | |||
38 | public function testImportWallabagBadFile() | ||
39 | { | ||
40 | $this->logInAs('admin'); | ||
41 | $client = $this->getClient(); | ||
42 | |||
43 | $crawler = $client->request('GET', '/import/wallabag-v1'); | ||
44 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
45 | |||
46 | $data = [ | ||
47 | 'upload_import_file[file]' => '', | ||
48 | ]; | ||
49 | |||
50 | $client->submit($form, $data); | ||
51 | |||
52 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
53 | } | ||
54 | |||
55 | public function testImportWallabagWithRedisEnabled() | ||
56 | { | ||
57 | $this->checkRedis(); | ||
58 | $this->logInAs('admin'); | ||
59 | $client = $this->getClient(); | ||
60 | |||
61 | $client->getContainer()->get('craue_config')->set('import_with_redis', 1); | ||
62 | |||
63 | $crawler = $client->request('GET', '/import/wallabag-v1'); | ||
64 | |||
65 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
66 | $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count()); | ||
67 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | ||
68 | |||
69 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
70 | |||
71 | $file = new UploadedFile(__DIR__.'/../fixtures/wallabag-v1.json', 'wallabag-v1.json'); | ||
72 | |||
73 | $data = [ | ||
74 | 'upload_import_file[file]' => $file, | ||
75 | ]; | ||
76 | |||
77 | $client->submit($form, $data); | ||
78 | |||
79 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
80 | |||
81 | $crawler = $client->followRedirect(); | ||
82 | |||
83 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); | ||
84 | $this->assertContains('flashes.import.notice.summary', $body[0]); | ||
85 | |||
86 | $this->assertNotEmpty($client->getContainer()->get('wallabag_core.redis.client')->lpop('wallabag.import.wallabag_v1')); | ||
87 | |||
88 | $client->getContainer()->get('craue_config')->set('import_with_redis', 0); | ||
89 | } | ||
90 | |||
22 | public function testImportWallabagWithFile() | 91 | public function testImportWallabagWithFile() |
23 | { | 92 | { |
24 | $this->logInAs('admin'); | 93 | $this->logInAs('admin'); |
@@ -56,6 +125,12 @@ class WallabagV1ControllerTest extends WallabagCoreTestCase | |||
56 | 125 | ||
57 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); | 126 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); |
58 | $this->assertContains('flashes.import.notice.summary', $body[0]); | 127 | $this->assertContains('flashes.import.notice.summary', $body[0]); |
128 | |||
129 | $this->assertEmpty($content->getMimetype()); | ||
130 | $this->assertEmpty($content->getPreviewPicture()); | ||
131 | $this->assertEmpty($content->getLanguage()); | ||
132 | $this->assertEquals(1, count($content->getTags())); | ||
133 | $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt()); | ||
59 | } | 134 | } |
60 | 135 | ||
61 | public function testImportWallabagWithFileAndMarkAllAsRead() | 136 | public function testImportWallabagWithFileAndMarkAllAsRead() |
diff --git a/tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php b/tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php index d8d2c8bf..27d2d52b 100644 --- a/tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php +++ b/tests/Wallabag/ImportBundle/Controller/WallabagV2ControllerTest.php | |||
@@ -19,6 +19,75 @@ class WallabagV2ControllerTest extends WallabagCoreTestCase | |||
19 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | 19 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); |
20 | } | 20 | } |
21 | 21 | ||
22 | public function testImportWallabagWithRabbitEnabled() | ||
23 | { | ||
24 | $this->logInAs('admin'); | ||
25 | $client = $this->getClient(); | ||
26 | |||
27 | $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 1); | ||
28 | |||
29 | $crawler = $client->request('GET', '/import/wallabag-v2'); | ||
30 | |||
31 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
32 | $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count()); | ||
33 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | ||
34 | |||
35 | $client->getContainer()->get('craue_config')->set('import_with_rabbitmq', 0); | ||
36 | } | ||
37 | |||
38 | public function testImportWallabagBadFile() | ||
39 | { | ||
40 | $this->logInAs('admin'); | ||
41 | $client = $this->getClient(); | ||
42 | |||
43 | $crawler = $client->request('GET', '/import/wallabag-v2'); | ||
44 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
45 | |||
46 | $data = [ | ||
47 | 'upload_import_file[file]' => '', | ||
48 | ]; | ||
49 | |||
50 | $client->submit($form, $data); | ||
51 | |||
52 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
53 | } | ||
54 | |||
55 | public function testImportWallabagWithRedisEnabled() | ||
56 | { | ||
57 | $this->checkRedis(); | ||
58 | $this->logInAs('admin'); | ||
59 | $client = $this->getClient(); | ||
60 | |||
61 | $client->getContainer()->get('craue_config')->set('import_with_redis', 1); | ||
62 | |||
63 | $crawler = $client->request('GET', '/import/wallabag-v2'); | ||
64 | |||
65 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); | ||
66 | $this->assertEquals(1, $crawler->filter('form[name=upload_import_file] > button[type=submit]')->count()); | ||
67 | $this->assertEquals(1, $crawler->filter('input[type=file]')->count()); | ||
68 | |||
69 | $form = $crawler->filter('form[name=upload_import_file] > button[type=submit]')->form(); | ||
70 | |||
71 | $file = new UploadedFile(__DIR__.'/../fixtures/wallabag-v2.json', 'wallabag-v2.json'); | ||
72 | |||
73 | $data = [ | ||
74 | 'upload_import_file[file]' => $file, | ||
75 | ]; | ||
76 | |||
77 | $client->submit($form, $data); | ||
78 | |||
79 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); | ||
80 | |||
81 | $crawler = $client->followRedirect(); | ||
82 | |||
83 | $this->assertGreaterThan(1, $body = $crawler->filter('body')->extract(['_text'])); | ||
84 | $this->assertContains('flashes.import.notice.summary', $body[0]); | ||
85 | |||
86 | $this->assertNotEmpty($client->getContainer()->get('wallabag_core.redis.client')->lpop('wallabag.import.wallabag_v2')); | ||
87 | |||
88 | $client->getContainer()->get('craue_config')->set('import_with_redis', 0); | ||
89 | } | ||
90 | |||
22 | public function testImportWallabagWithFile() | 91 | public function testImportWallabagWithFile() |
23 | { | 92 | { |
24 | $this->logInAs('admin'); | 93 | $this->logInAs('admin'); |
@@ -50,9 +119,9 @@ class WallabagV2ControllerTest extends WallabagCoreTestCase | |||
50 | $this->getLoggedInUserId() | 119 | $this->getLoggedInUserId() |
51 | ); | 120 | ); |
52 | 121 | ||
53 | $this->assertEmpty($content->getMimetype()); | 122 | $this->assertNotEmpty($content->getMimetype()); |
54 | $this->assertEmpty($content->getPreviewPicture()); | 123 | $this->assertNotEmpty($content->getPreviewPicture()); |
55 | $this->assertEmpty($content->getLanguage()); | 124 | $this->assertNotEmpty($content->getLanguage()); |
56 | $this->assertEquals(0, count($content->getTags())); | 125 | $this->assertEquals(0, count($content->getTags())); |
57 | 126 | ||
58 | $content = $client->getContainer() | 127 | $content = $client->getContainer() |
@@ -67,6 +136,8 @@ class WallabagV2ControllerTest extends WallabagCoreTestCase | |||
67 | $this->assertNotEmpty($content->getPreviewPicture()); | 136 | $this->assertNotEmpty($content->getPreviewPicture()); |
68 | $this->assertNotEmpty($content->getLanguage()); | 137 | $this->assertNotEmpty($content->getLanguage()); |
69 | $this->assertEquals(2, count($content->getTags())); | 138 | $this->assertEquals(2, count($content->getTags())); |
139 | $this->assertInstanceOf(\DateTime::class, $content->getCreatedAt()); | ||
140 | $this->assertEquals('2016-09-08', $content->getCreatedAt()->format('Y-m-d')); | ||
70 | } | 141 | } |
71 | 142 | ||
72 | public function testImportWallabagWithEmptyFile() | 143 | public function testImportWallabagWithEmptyFile() |
diff --git a/tests/Wallabag/ImportBundle/Import/ChromeImportTest.php b/tests/Wallabag/ImportBundle/Import/ChromeImportTest.php new file mode 100644 index 00000000..1e52615c --- /dev/null +++ b/tests/Wallabag/ImportBundle/Import/ChromeImportTest.php | |||
@@ -0,0 +1,233 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Tests\Wallabag\ImportBundle\Import; | ||
4 | |||
5 | use Wallabag\ImportBundle\Import\ChromeImport; | ||
6 | use Wallabag\UserBundle\Entity\User; | ||
7 | use Wallabag\CoreBundle\Entity\Entry; | ||
8 | use Wallabag\ImportBundle\Redis\Producer; | ||
9 | use Monolog\Logger; | ||
10 | use Monolog\Handler\TestHandler; | ||
11 | use Simpleue\Queue\RedisQueue; | ||
12 | use M6Web\Component\RedisMock\RedisMockFactory; | ||
13 | |||
14 | class ChromeImportTest extends \PHPUnit_Framework_TestCase | ||
15 | { | ||
16 | protected $user; | ||
17 | protected $em; | ||
18 | protected $logHandler; | ||
19 | protected $contentProxy; | ||
20 | |||
21 | private function getChromeImport($unsetUser = false) | ||
22 | { | ||
23 | $this->user = new User(); | ||
24 | |||
25 | $this->em = $this->getMockBuilder('Doctrine\ORM\EntityManager') | ||
26 | ->disableOriginalConstructor() | ||
27 | ->getMock(); | ||
28 | |||
29 | $this->contentProxy = $this->getMockBuilder('Wallabag\CoreBundle\Helper\ContentProxy') | ||
30 | ->disableOriginalConstructor() | ||
31 | ->getMock(); | ||
32 | |||
33 | $wallabag = new ChromeImport($this->em, $this->contentProxy); | ||
34 | |||
35 | $this->logHandler = new TestHandler(); | ||
36 | $logger = new Logger('test', [$this->logHandler]); | ||
37 | $wallabag->setLogger($logger); | ||
38 | |||
39 | if (false === $unsetUser) { | ||
40 | $wallabag->setUser($this->user); | ||
41 | } | ||
42 | |||
43 | return $wallabag; | ||
44 | } | ||
45 | |||
46 | public function testInit() | ||
47 | { | ||
48 | $chromeImport = $this->getChromeImport(); | ||
49 | |||
50 | $this->assertEquals('Chrome', $chromeImport->getName()); | ||
51 | $this->assertNotEmpty($chromeImport->getUrl()); | ||
52 | $this->assertEquals('import.chrome.description', $chromeImport->getDescription()); | ||
53 | } | ||
54 | |||
55 | public function testImport() | ||
56 | { | ||
57 | $chromeImport = $this->getChromeImport(); | ||
58 | $chromeImport->setFilepath(__DIR__.'/../fixtures/chrome-bookmarks'); | ||
59 | |||
60 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
61 | ->disableOriginalConstructor() | ||
62 | ->getMock(); | ||
63 | |||
64 | $entryRepo->expects($this->exactly(1)) | ||
65 | ->method('findByUrlAndUserId') | ||
66 | ->willReturn(false); | ||
67 | |||
68 | $this->em | ||
69 | ->expects($this->any()) | ||
70 | ->method('getRepository') | ||
71 | ->willReturn($entryRepo); | ||
72 | |||
73 | $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry') | ||
74 | ->disableOriginalConstructor() | ||
75 | ->getMock(); | ||
76 | |||
77 | $this->contentProxy | ||
78 | ->expects($this->exactly(1)) | ||
79 | ->method('updateEntry') | ||
80 | ->willReturn($entry); | ||
81 | |||
82 | $res = $chromeImport->import(); | ||
83 | |||
84 | $this->assertTrue($res); | ||
85 | $this->assertEquals(['skipped' => 0, 'imported' => 1, 'queued' => 0], $chromeImport->getSummary()); | ||
86 | } | ||
87 | |||
88 | public function testImportAndMarkAllAsRead() | ||
89 | { | ||
90 | $chromeImport = $this->getChromeImport(); | ||
91 | $chromeImport->setFilepath(__DIR__.'/../fixtures/chrome-bookmarks'); | ||
92 | |||
93 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
94 | ->disableOriginalConstructor() | ||
95 | ->getMock(); | ||
96 | |||
97 | $entryRepo->expects($this->exactly(1)) | ||
98 | ->method('findByUrlAndUserId') | ||
99 | ->will($this->onConsecutiveCalls(false, true)); | ||
100 | |||
101 | $this->em | ||
102 | ->expects($this->any()) | ||
103 | ->method('getRepository') | ||
104 | ->willReturn($entryRepo); | ||
105 | |||
106 | $this->contentProxy | ||
107 | ->expects($this->exactly(1)) | ||
108 | ->method('updateEntry') | ||
109 | ->willReturn(new Entry($this->user)); | ||
110 | |||
111 | // check that every entry persisted are archived | ||
112 | $this->em | ||
113 | ->expects($this->any()) | ||
114 | ->method('persist') | ||
115 | ->with($this->callback(function ($persistedEntry) { | ||
116 | return $persistedEntry->isArchived(); | ||
117 | })); | ||
118 | |||
119 | $res = $chromeImport->setMarkAsRead(true)->import(); | ||
120 | |||
121 | $this->assertTrue($res); | ||
122 | |||
123 | $this->assertEquals(['skipped' => 0, 'imported' => 1, 'queued' => 0], $chromeImport->getSummary()); | ||
124 | } | ||
125 | |||
126 | public function testImportWithRabbit() | ||
127 | { | ||
128 | $chromeImport = $this->getChromeImport(); | ||
129 | $chromeImport->setFilepath(__DIR__.'/../fixtures/chrome-bookmarks'); | ||
130 | |||
131 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
132 | ->disableOriginalConstructor() | ||
133 | ->getMock(); | ||
134 | |||
135 | $entryRepo->expects($this->never()) | ||
136 | ->method('findByUrlAndUserId'); | ||
137 | |||
138 | $this->em | ||
139 | ->expects($this->never()) | ||
140 | ->method('getRepository'); | ||
141 | |||
142 | $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry') | ||
143 | ->disableOriginalConstructor() | ||
144 | ->getMock(); | ||
145 | |||
146 | $this->contentProxy | ||
147 | ->expects($this->never()) | ||
148 | ->method('updateEntry'); | ||
149 | |||
150 | $producer = $this->getMockBuilder('OldSound\RabbitMqBundle\RabbitMq\Producer') | ||
151 | ->disableOriginalConstructor() | ||
152 | ->getMock(); | ||
153 | |||
154 | $producer | ||
155 | ->expects($this->exactly(1)) | ||
156 | ->method('publish'); | ||
157 | |||
158 | $chromeImport->setProducer($producer); | ||
159 | |||
160 | $res = $chromeImport->setMarkAsRead(true)->import(); | ||
161 | |||
162 | $this->assertTrue($res); | ||
163 | $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 1], $chromeImport->getSummary()); | ||
164 | } | ||
165 | |||
166 | public function testImportWithRedis() | ||
167 | { | ||
168 | $chromeImport = $this->getChromeImport(); | ||
169 | $chromeImport->setFilepath(__DIR__.'/../fixtures/chrome-bookmarks'); | ||
170 | |||
171 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
172 | ->disableOriginalConstructor() | ||
173 | ->getMock(); | ||
174 | |||
175 | $entryRepo->expects($this->never()) | ||
176 | ->method('findByUrlAndUserId'); | ||
177 | |||
178 | $this->em | ||
179 | ->expects($this->never()) | ||
180 | ->method('getRepository'); | ||
181 | |||
182 | $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry') | ||
183 | ->disableOriginalConstructor() | ||
184 | ->getMock(); | ||
185 | |||
186 | $this->contentProxy | ||
187 | ->expects($this->never()) | ||
188 | ->method('updateEntry'); | ||
189 | |||
190 | $factory = new RedisMockFactory(); | ||
191 | $redisMock = $factory->getAdapter('Predis\Client', true); | ||
192 | |||
193 | $queue = new RedisQueue($redisMock, 'chrome'); | ||
194 | $producer = new Producer($queue); | ||
195 | |||
196 | $chromeImport->setProducer($producer); | ||
197 | |||
198 | $res = $chromeImport->setMarkAsRead(true)->import(); | ||
199 | |||
200 | $this->assertTrue($res); | ||
201 | $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 1], $chromeImport->getSummary()); | ||
202 | |||
203 | $this->assertNotEmpty($redisMock->lpop('chrome')); | ||
204 | } | ||
205 | |||
206 | public function testImportBadFile() | ||
207 | { | ||
208 | $chromeImport = $this->getChromeImport(); | ||
209 | $chromeImport->setFilepath(__DIR__.'/../fixtures/wallabag-v1.jsonx'); | ||
210 | |||
211 | $res = $chromeImport->import(); | ||
212 | |||
213 | $this->assertFalse($res); | ||
214 | |||
215 | $records = $this->logHandler->getRecords(); | ||
216 | $this->assertContains('Wallabag Browser Import: unable to read file', $records[0]['message']); | ||
217 | $this->assertEquals('ERROR', $records[0]['level_name']); | ||
218 | } | ||
219 | |||
220 | public function testImportUserNotDefined() | ||
221 | { | ||
222 | $chromeImport = $this->getChromeImport(true); | ||
223 | $chromeImport->setFilepath(__DIR__.'/../fixtures/chrome-bookmarks'); | ||
224 | |||
225 | $res = $chromeImport->import(); | ||
226 | |||
227 | $this->assertFalse($res); | ||
228 | |||
229 | $records = $this->logHandler->getRecords(); | ||
230 | $this->assertContains('Wallabag Browser Import: user is not defined', $records[0]['message']); | ||
231 | $this->assertEquals('ERROR', $records[0]['level_name']); | ||
232 | } | ||
233 | } | ||
diff --git a/tests/Wallabag/ImportBundle/Import/FirefoxImportTest.php b/tests/Wallabag/ImportBundle/Import/FirefoxImportTest.php new file mode 100644 index 00000000..007dda6a --- /dev/null +++ b/tests/Wallabag/ImportBundle/Import/FirefoxImportTest.php | |||
@@ -0,0 +1,233 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Tests\Wallabag\ImportBundle\Import; | ||
4 | |||
5 | use Wallabag\ImportBundle\Import\FirefoxImport; | ||
6 | use Wallabag\UserBundle\Entity\User; | ||
7 | use Wallabag\CoreBundle\Entity\Entry; | ||
8 | use Wallabag\ImportBundle\Redis\Producer; | ||
9 | use Monolog\Logger; | ||
10 | use Monolog\Handler\TestHandler; | ||
11 | use Simpleue\Queue\RedisQueue; | ||
12 | use M6Web\Component\RedisMock\RedisMockFactory; | ||
13 | |||
14 | class FirefoxImportTest extends \PHPUnit_Framework_TestCase | ||
15 | { | ||
16 | protected $user; | ||
17 | protected $em; | ||
18 | protected $logHandler; | ||
19 | protected $contentProxy; | ||
20 | |||
21 | private function getFirefoxImport($unsetUser = false) | ||
22 | { | ||
23 | $this->user = new User(); | ||
24 | |||
25 | $this->em = $this->getMockBuilder('Doctrine\ORM\EntityManager') | ||
26 | ->disableOriginalConstructor() | ||
27 | ->getMock(); | ||
28 | |||
29 | $this->contentProxy = $this->getMockBuilder('Wallabag\CoreBundle\Helper\ContentProxy') | ||
30 | ->disableOriginalConstructor() | ||
31 | ->getMock(); | ||
32 | |||
33 | $wallabag = new FirefoxImport($this->em, $this->contentProxy); | ||
34 | |||
35 | $this->logHandler = new TestHandler(); | ||
36 | $logger = new Logger('test', [$this->logHandler]); | ||
37 | $wallabag->setLogger($logger); | ||
38 | |||
39 | if (false === $unsetUser) { | ||
40 | $wallabag->setUser($this->user); | ||
41 | } | ||
42 | |||
43 | return $wallabag; | ||
44 | } | ||
45 | |||
46 | public function testInit() | ||
47 | { | ||
48 | $firefoxImport = $this->getFirefoxImport(); | ||
49 | |||
50 | $this->assertEquals('Firefox', $firefoxImport->getName()); | ||
51 | $this->assertNotEmpty($firefoxImport->getUrl()); | ||
52 | $this->assertEquals('import.firefox.description', $firefoxImport->getDescription()); | ||
53 | } | ||
54 | |||
55 | public function testImport() | ||
56 | { | ||
57 | $firefoxImport = $this->getFirefoxImport(); | ||
58 | $firefoxImport->setFilepath(__DIR__.'/../fixtures/firefox-bookmarks.json'); | ||
59 | |||
60 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
61 | ->disableOriginalConstructor() | ||
62 | ->getMock(); | ||
63 | |||
64 | $entryRepo->expects($this->exactly(2)) | ||
65 | ->method('findByUrlAndUserId') | ||
66 | ->willReturn(false); | ||
67 | |||
68 | $this->em | ||
69 | ->expects($this->any()) | ||
70 | ->method('getRepository') | ||
71 | ->willReturn($entryRepo); | ||
72 | |||
73 | $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry') | ||
74 | ->disableOriginalConstructor() | ||
75 | ->getMock(); | ||
76 | |||
77 | $this->contentProxy | ||
78 | ->expects($this->exactly(2)) | ||
79 | ->method('updateEntry') | ||
80 | ->willReturn($entry); | ||
81 | |||
82 | $res = $firefoxImport->import(); | ||
83 | |||
84 | $this->assertTrue($res); | ||
85 | $this->assertEquals(['skipped' => 0, 'imported' => 2, 'queued' => 0], $firefoxImport->getSummary()); | ||
86 | } | ||
87 | |||
88 | public function testImportAndMarkAllAsRead() | ||
89 | { | ||
90 | $firefoxImport = $this->getFirefoxImport(); | ||
91 | $firefoxImport->setFilepath(__DIR__.'/../fixtures/firefox-bookmarks.json'); | ||
92 | |||
93 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
94 | ->disableOriginalConstructor() | ||
95 | ->getMock(); | ||
96 | |||
97 | $entryRepo->expects($this->exactly(2)) | ||
98 | ->method('findByUrlAndUserId') | ||
99 | ->will($this->onConsecutiveCalls(false, true)); | ||
100 | |||
101 | $this->em | ||
102 | ->expects($this->any()) | ||
103 | ->method('getRepository') | ||
104 | ->willReturn($entryRepo); | ||
105 | |||
106 | $this->contentProxy | ||
107 | ->expects($this->exactly(1)) | ||
108 | ->method('updateEntry') | ||
109 | ->willReturn(new Entry($this->user)); | ||
110 | |||
111 | // check that every entry persisted are archived | ||
112 | $this->em | ||
113 | ->expects($this->any()) | ||
114 | ->method('persist') | ||
115 | ->with($this->callback(function ($persistedEntry) { | ||
116 | return $persistedEntry->isArchived(); | ||
117 | })); | ||
118 | |||
119 | $res = $firefoxImport->setMarkAsRead(true)->import(); | ||
120 | |||
121 | $this->assertTrue($res); | ||
122 | |||
123 | $this->assertEquals(['skipped' => 1, 'imported' => 1, 'queued' => 0], $firefoxImport->getSummary()); | ||
124 | } | ||
125 | |||
126 | public function testImportWithRabbit() | ||
127 | { | ||
128 | $firefoxImport = $this->getFirefoxImport(); | ||
129 | $firefoxImport->setFilepath(__DIR__.'/../fixtures/firefox-bookmarks.json'); | ||
130 | |||
131 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
132 | ->disableOriginalConstructor() | ||
133 | ->getMock(); | ||
134 | |||
135 | $entryRepo->expects($this->never()) | ||
136 | ->method('findByUrlAndUserId'); | ||
137 | |||
138 | $this->em | ||
139 | ->expects($this->never()) | ||
140 | ->method('getRepository'); | ||
141 | |||
142 | $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry') | ||
143 | ->disableOriginalConstructor() | ||
144 | ->getMock(); | ||
145 | |||
146 | $this->contentProxy | ||
147 | ->expects($this->never()) | ||
148 | ->method('updateEntry'); | ||
149 | |||
150 | $producer = $this->getMockBuilder('OldSound\RabbitMqBundle\RabbitMq\Producer') | ||
151 | ->disableOriginalConstructor() | ||
152 | ->getMock(); | ||
153 | |||
154 | $producer | ||
155 | ->expects($this->exactly(1)) | ||
156 | ->method('publish'); | ||
157 | |||
158 | $firefoxImport->setProducer($producer); | ||
159 | |||
160 | $res = $firefoxImport->setMarkAsRead(true)->import(); | ||
161 | |||
162 | $this->assertTrue($res); | ||
163 | $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 1], $firefoxImport->getSummary()); | ||
164 | } | ||
165 | |||
166 | public function testImportWithRedis() | ||
167 | { | ||
168 | $firefoxImport = $this->getFirefoxImport(); | ||
169 | $firefoxImport->setFilepath(__DIR__.'/../fixtures/firefox-bookmarks.json'); | ||
170 | |||
171 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
172 | ->disableOriginalConstructor() | ||
173 | ->getMock(); | ||
174 | |||
175 | $entryRepo->expects($this->never()) | ||
176 | ->method('findByUrlAndUserId'); | ||
177 | |||
178 | $this->em | ||
179 | ->expects($this->never()) | ||
180 | ->method('getRepository'); | ||
181 | |||
182 | $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry') | ||
183 | ->disableOriginalConstructor() | ||
184 | ->getMock(); | ||
185 | |||
186 | $this->contentProxy | ||
187 | ->expects($this->never()) | ||
188 | ->method('updateEntry'); | ||
189 | |||
190 | $factory = new RedisMockFactory(); | ||
191 | $redisMock = $factory->getAdapter('Predis\Client', true); | ||
192 | |||
193 | $queue = new RedisQueue($redisMock, 'firefox'); | ||
194 | $producer = new Producer($queue); | ||
195 | |||
196 | $firefoxImport->setProducer($producer); | ||
197 | |||
198 | $res = $firefoxImport->setMarkAsRead(true)->import(); | ||
199 | |||
200 | $this->assertTrue($res); | ||
201 | $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 1], $firefoxImport->getSummary()); | ||
202 | |||
203 | $this->assertNotEmpty($redisMock->lpop('firefox')); | ||
204 | } | ||
205 | |||
206 | public function testImportBadFile() | ||
207 | { | ||
208 | $firefoxImport = $this->getFirefoxImport(); | ||
209 | $firefoxImport->setFilepath(__DIR__.'/../fixtures/wallabag-v1.jsonx'); | ||
210 | |||
211 | $res = $firefoxImport->import(); | ||
212 | |||
213 | $this->assertFalse($res); | ||
214 | |||
215 | $records = $this->logHandler->getRecords(); | ||
216 | $this->assertContains('Wallabag Browser Import: unable to read file', $records[0]['message']); | ||
217 | $this->assertEquals('ERROR', $records[0]['level_name']); | ||
218 | } | ||
219 | |||
220 | public function testImportUserNotDefined() | ||
221 | { | ||
222 | $firefoxImport = $this->getFirefoxImport(true); | ||
223 | $firefoxImport->setFilepath(__DIR__.'/../fixtures/firefox-bookmarks.json'); | ||
224 | |||
225 | $res = $firefoxImport->import(); | ||
226 | |||
227 | $this->assertFalse($res); | ||
228 | |||
229 | $records = $this->logHandler->getRecords(); | ||
230 | $this->assertContains('Wallabag Browser Import: user is not defined', $records[0]['message']); | ||
231 | $this->assertEquals('ERROR', $records[0]['level_name']); | ||
232 | } | ||
233 | } | ||
diff --git a/tests/Wallabag/ImportBundle/Import/InstapaperImportTest.php b/tests/Wallabag/ImportBundle/Import/InstapaperImportTest.php new file mode 100644 index 00000000..75900bd7 --- /dev/null +++ b/tests/Wallabag/ImportBundle/Import/InstapaperImportTest.php | |||
@@ -0,0 +1,233 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Tests\Wallabag\ImportBundle\Import; | ||
4 | |||
5 | use Wallabag\ImportBundle\Import\InstapaperImport; | ||
6 | use Wallabag\UserBundle\Entity\User; | ||
7 | use Wallabag\CoreBundle\Entity\Entry; | ||
8 | use Wallabag\ImportBundle\Redis\Producer; | ||
9 | use Monolog\Logger; | ||
10 | use Monolog\Handler\TestHandler; | ||
11 | use Simpleue\Queue\RedisQueue; | ||
12 | use M6Web\Component\RedisMock\RedisMockFactory; | ||
13 | |||
14 | class InstapaperImportTest extends \PHPUnit_Framework_TestCase | ||
15 | { | ||
16 | protected $user; | ||
17 | protected $em; | ||
18 | protected $logHandler; | ||
19 | protected $contentProxy; | ||
20 | |||
21 | private function getInstapaperImport($unsetUser = false) | ||
22 | { | ||
23 | $this->user = new User(); | ||
24 | |||
25 | $this->em = $this->getMockBuilder('Doctrine\ORM\EntityManager') | ||
26 | ->disableOriginalConstructor() | ||
27 | ->getMock(); | ||
28 | |||
29 | $this->contentProxy = $this->getMockBuilder('Wallabag\CoreBundle\Helper\ContentProxy') | ||
30 | ->disableOriginalConstructor() | ||
31 | ->getMock(); | ||
32 | |||
33 | $import = new InstapaperImport($this->em, $this->contentProxy); | ||
34 | |||
35 | $this->logHandler = new TestHandler(); | ||
36 | $logger = new Logger('test', [$this->logHandler]); | ||
37 | $import->setLogger($logger); | ||
38 | |||
39 | if (false === $unsetUser) { | ||
40 | $import->setUser($this->user); | ||
41 | } | ||
42 | |||
43 | return $import; | ||
44 | } | ||
45 | |||
46 | public function testInit() | ||
47 | { | ||
48 | $instapaperImport = $this->getInstapaperImport(); | ||
49 | |||
50 | $this->assertEquals('Instapaper', $instapaperImport->getName()); | ||
51 | $this->assertNotEmpty($instapaperImport->getUrl()); | ||
52 | $this->assertEquals('import.instapaper.description', $instapaperImport->getDescription()); | ||
53 | } | ||
54 | |||
55 | public function testImport() | ||
56 | { | ||
57 | $instapaperImport = $this->getInstapaperImport(); | ||
58 | $instapaperImport->setFilepath(__DIR__.'/../fixtures/instapaper-export.csv'); | ||
59 | |||
60 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
61 | ->disableOriginalConstructor() | ||
62 | ->getMock(); | ||
63 | |||
64 | $entryRepo->expects($this->exactly(3)) | ||
65 | ->method('findByUrlAndUserId') | ||
66 | ->willReturn(false); | ||
67 | |||
68 | $this->em | ||
69 | ->expects($this->any()) | ||
70 | ->method('getRepository') | ||
71 | ->willReturn($entryRepo); | ||
72 | |||
73 | $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry') | ||
74 | ->disableOriginalConstructor() | ||
75 | ->getMock(); | ||
76 | |||
77 | $this->contentProxy | ||
78 | ->expects($this->exactly(3)) | ||
79 | ->method('updateEntry') | ||
80 | ->willReturn($entry); | ||
81 | |||
82 | $res = $instapaperImport->import(); | ||
83 | |||
84 | $this->assertTrue($res); | ||
85 | $this->assertEquals(['skipped' => 0, 'imported' => 3, 'queued' => 0], $instapaperImport->getSummary()); | ||
86 | } | ||
87 | |||
88 | public function testImportAndMarkAllAsRead() | ||
89 | { | ||
90 | $instapaperImport = $this->getInstapaperImport(); | ||
91 | $instapaperImport->setFilepath(__DIR__.'/../fixtures/instapaper-export.csv'); | ||
92 | |||
93 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
94 | ->disableOriginalConstructor() | ||
95 | ->getMock(); | ||
96 | |||
97 | $entryRepo->expects($this->exactly(3)) | ||
98 | ->method('findByUrlAndUserId') | ||
99 | ->will($this->onConsecutiveCalls(false, true, true)); | ||
100 | |||
101 | $this->em | ||
102 | ->expects($this->any()) | ||
103 | ->method('getRepository') | ||
104 | ->willReturn($entryRepo); | ||
105 | |||
106 | $this->contentProxy | ||
107 | ->expects($this->once()) | ||
108 | ->method('updateEntry') | ||
109 | ->willReturn(new Entry($this->user)); | ||
110 | |||
111 | // check that every entry persisted are archived | ||
112 | $this->em | ||
113 | ->expects($this->once()) | ||
114 | ->method('persist') | ||
115 | ->with($this->callback(function ($persistedEntry) { | ||
116 | return $persistedEntry->isArchived(); | ||
117 | })); | ||
118 | |||
119 | $res = $instapaperImport->setMarkAsRead(true)->import(); | ||
120 | |||
121 | $this->assertTrue($res); | ||
122 | |||
123 | $this->assertEquals(['skipped' => 2, 'imported' => 1, 'queued' => 0], $instapaperImport->getSummary()); | ||
124 | } | ||
125 | |||
126 | public function testImportWithRabbit() | ||
127 | { | ||
128 | $instapaperImport = $this->getInstapaperImport(); | ||
129 | $instapaperImport->setFilepath(__DIR__.'/../fixtures/instapaper-export.csv'); | ||
130 | |||
131 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
132 | ->disableOriginalConstructor() | ||
133 | ->getMock(); | ||
134 | |||
135 | $entryRepo->expects($this->never()) | ||
136 | ->method('findByUrlAndUserId'); | ||
137 | |||
138 | $this->em | ||
139 | ->expects($this->never()) | ||
140 | ->method('getRepository'); | ||
141 | |||
142 | $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry') | ||
143 | ->disableOriginalConstructor() | ||
144 | ->getMock(); | ||
145 | |||
146 | $this->contentProxy | ||
147 | ->expects($this->never()) | ||
148 | ->method('updateEntry'); | ||
149 | |||
150 | $producer = $this->getMockBuilder('OldSound\RabbitMqBundle\RabbitMq\Producer') | ||
151 | ->disableOriginalConstructor() | ||
152 | ->getMock(); | ||
153 | |||
154 | $producer | ||
155 | ->expects($this->exactly(3)) | ||
156 | ->method('publish'); | ||
157 | |||
158 | $instapaperImport->setProducer($producer); | ||
159 | |||
160 | $res = $instapaperImport->setMarkAsRead(true)->import(); | ||
161 | |||
162 | $this->assertTrue($res); | ||
163 | $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 3], $instapaperImport->getSummary()); | ||
164 | } | ||
165 | |||
166 | public function testImportWithRedis() | ||
167 | { | ||
168 | $instapaperImport = $this->getInstapaperImport(); | ||
169 | $instapaperImport->setFilepath(__DIR__.'/../fixtures/instapaper-export.csv'); | ||
170 | |||
171 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
172 | ->disableOriginalConstructor() | ||
173 | ->getMock(); | ||
174 | |||
175 | $entryRepo->expects($this->never()) | ||
176 | ->method('findByUrlAndUserId'); | ||
177 | |||
178 | $this->em | ||
179 | ->expects($this->never()) | ||
180 | ->method('getRepository'); | ||
181 | |||
182 | $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry') | ||
183 | ->disableOriginalConstructor() | ||
184 | ->getMock(); | ||
185 | |||
186 | $this->contentProxy | ||
187 | ->expects($this->never()) | ||
188 | ->method('updateEntry'); | ||
189 | |||
190 | $factory = new RedisMockFactory(); | ||
191 | $redisMock = $factory->getAdapter('Predis\Client', true); | ||
192 | |||
193 | $queue = new RedisQueue($redisMock, 'instapaper'); | ||
194 | $producer = new Producer($queue); | ||
195 | |||
196 | $instapaperImport->setProducer($producer); | ||
197 | |||
198 | $res = $instapaperImport->setMarkAsRead(true)->import(); | ||
199 | |||
200 | $this->assertTrue($res); | ||
201 | $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 3], $instapaperImport->getSummary()); | ||
202 | |||
203 | $this->assertNotEmpty($redisMock->lpop('instapaper')); | ||
204 | } | ||
205 | |||
206 | public function testImportBadFile() | ||
207 | { | ||
208 | $instapaperImport = $this->getInstapaperImport(); | ||
209 | $instapaperImport->setFilepath(__DIR__.'/../fixtures/wallabag-v1.jsonx'); | ||
210 | |||
211 | $res = $instapaperImport->import(); | ||
212 | |||
213 | $this->assertFalse($res); | ||
214 | |||
215 | $records = $this->logHandler->getRecords(); | ||
216 | $this->assertContains('InstapaperImport: unable to read file', $records[0]['message']); | ||
217 | $this->assertEquals('ERROR', $records[0]['level_name']); | ||
218 | } | ||
219 | |||
220 | public function testImportUserNotDefined() | ||
221 | { | ||
222 | $instapaperImport = $this->getInstapaperImport(true); | ||
223 | $instapaperImport->setFilepath(__DIR__.'/../fixtures/instapaper-export.csv'); | ||
224 | |||
225 | $res = $instapaperImport->import(); | ||
226 | |||
227 | $this->assertFalse($res); | ||
228 | |||
229 | $records = $this->logHandler->getRecords(); | ||
230 | $this->assertContains('InstapaperImport: user is not defined', $records[0]['message']); | ||
231 | $this->assertEquals('ERROR', $records[0]['level_name']); | ||
232 | } | ||
233 | } | ||
diff --git a/tests/Wallabag/ImportBundle/Import/PocketImportTest.php b/tests/Wallabag/ImportBundle/Import/PocketImportTest.php index 8534e1c8..9ec7935c 100644 --- a/tests/Wallabag/ImportBundle/Import/PocketImportTest.php +++ b/tests/Wallabag/ImportBundle/Import/PocketImportTest.php | |||
@@ -4,21 +4,17 @@ namespace Tests\Wallabag\ImportBundle\Import; | |||
4 | 4 | ||
5 | use Wallabag\UserBundle\Entity\User; | 5 | use Wallabag\UserBundle\Entity\User; |
6 | use Wallabag\CoreBundle\Entity\Entry; | 6 | use Wallabag\CoreBundle\Entity\Entry; |
7 | use Wallabag\CoreBundle\Entity\Config; | ||
7 | use Wallabag\ImportBundle\Import\PocketImport; | 8 | use Wallabag\ImportBundle\Import\PocketImport; |
8 | use GuzzleHttp\Client; | 9 | use GuzzleHttp\Client; |
9 | use GuzzleHttp\Subscriber\Mock; | 10 | use GuzzleHttp\Subscriber\Mock; |
10 | use GuzzleHttp\Message\Response; | 11 | use GuzzleHttp\Message\Response; |
11 | use GuzzleHttp\Stream\Stream; | 12 | use GuzzleHttp\Stream\Stream; |
13 | use Wallabag\ImportBundle\Redis\Producer; | ||
12 | use Monolog\Logger; | 14 | use Monolog\Logger; |
13 | use Monolog\Handler\TestHandler; | 15 | use Monolog\Handler\TestHandler; |
14 | 16 | use Simpleue\Queue\RedisQueue; | |
15 | class PocketImportMock extends PocketImport | 17 | use M6Web\Component\RedisMock\RedisMockFactory; |
16 | { | ||
17 | public function getAccessToken() | ||
18 | { | ||
19 | return $this->accessToken; | ||
20 | } | ||
21 | } | ||
22 | 18 | ||
23 | class PocketImportTest extends \PHPUnit_Framework_TestCase | 19 | class PocketImportTest extends \PHPUnit_Framework_TestCase |
24 | { | 20 | { |
@@ -32,45 +28,38 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase | |||
32 | { | 28 | { |
33 | $this->user = new User(); | 29 | $this->user = new User(); |
34 | 30 | ||
35 | $this->tokenStorage = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface') | 31 | $config = new Config($this->user); |
36 | ->disableOriginalConstructor() | 32 | $config->setPocketConsumerKey('xxx'); |
37 | ->getMock(); | ||
38 | 33 | ||
39 | $token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface') | 34 | $this->user->setConfig($config); |
40 | ->disableOriginalConstructor() | ||
41 | ->getMock(); | ||
42 | 35 | ||
43 | $this->contentProxy = $this->getMockBuilder('Wallabag\CoreBundle\Helper\ContentProxy') | 36 | $this->contentProxy = $this->getMockBuilder('Wallabag\CoreBundle\Helper\ContentProxy') |
44 | ->disableOriginalConstructor() | 37 | ->disableOriginalConstructor() |
45 | ->getMock(); | 38 | ->getMock(); |
46 | 39 | ||
47 | $token->expects($this->once()) | ||
48 | ->method('getUser') | ||
49 | ->willReturn($this->user); | ||
50 | |||
51 | $this->tokenStorage->expects($this->once()) | ||
52 | ->method('getToken') | ||
53 | ->willReturn($token); | ||
54 | |||
55 | $this->em = $this->getMockBuilder('Doctrine\ORM\EntityManager') | 40 | $this->em = $this->getMockBuilder('Doctrine\ORM\EntityManager') |
56 | ->disableOriginalConstructor() | 41 | ->disableOriginalConstructor() |
57 | ->getMock(); | 42 | ->getMock(); |
58 | 43 | ||
59 | $config = $this->getMockBuilder('Craue\ConfigBundle\Util\Config') | 44 | $this->uow = $this->getMockBuilder('Doctrine\ORM\UnitOfWork') |
60 | ->disableOriginalConstructor() | 45 | ->disableOriginalConstructor() |
61 | ->getMock(); | 46 | ->getMock(); |
62 | 47 | ||
63 | $config->expects($this->any()) | 48 | $this->em |
64 | ->method('get') | 49 | ->expects($this->any()) |
65 | ->with('pocket_consumer_key') | 50 | ->method('getUnitOfWork') |
66 | ->willReturn($consumerKey); | 51 | ->willReturn($this->uow); |
52 | |||
53 | $this->uow | ||
54 | ->expects($this->any()) | ||
55 | ->method('getScheduledEntityInsertions') | ||
56 | ->willReturn([]); | ||
67 | 57 | ||
68 | $pocket = new PocketImportMock( | 58 | $pocket = new PocketImport( |
69 | $this->tokenStorage, | ||
70 | $this->em, | 59 | $this->em, |
71 | $this->contentProxy, | 60 | $this->contentProxy |
72 | $config | ||
73 | ); | 61 | ); |
62 | $pocket->setUser($this->user); | ||
74 | 63 | ||
75 | $this->logHandler = new TestHandler(); | 64 | $this->logHandler = new TestHandler(); |
76 | $logger = new Logger('test', [$this->logHandler]); | 65 | $logger = new Logger('test', [$this->logHandler]); |
@@ -189,10 +178,16 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase | |||
189 | "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland", | 178 | "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland", |
190 | "favorite": "1", | 179 | "favorite": "1", |
191 | "status": "1", | 180 | "status": "1", |
181 | "time_added": "1473020899", | ||
182 | "time_updated": "1473020899", | ||
183 | "time_read": "0", | ||
184 | "time_favorited": "0", | ||
185 | "sort_id": 0, | ||
192 | "resolved_title": "The Massive Ryder Cup Preview", | 186 | "resolved_title": "The Massive Ryder Cup Preview", |
193 | "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview", | 187 | "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview", |
194 | "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.", | 188 | "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.", |
195 | "is_article": "1", | 189 | "is_article": "1", |
190 | "is_index": "0", | ||
196 | "has_video": "1", | 191 | "has_video": "1", |
197 | "has_image": "1", | 192 | "has_image": "1", |
198 | "word_count": "3197", | 193 | "word_count": "3197", |
@@ -236,10 +231,16 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase | |||
236 | "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland", | 231 | "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland", |
237 | "favorite": "1", | 232 | "favorite": "1", |
238 | "status": "1", | 233 | "status": "1", |
234 | "time_added": "1473020899", | ||
235 | "time_updated": "1473020899", | ||
236 | "time_read": "0", | ||
237 | "time_favorited": "0", | ||
238 | "sort_id": 1, | ||
239 | "resolved_title": "The Massive Ryder Cup Preview", | 239 | "resolved_title": "The Massive Ryder Cup Preview", |
240 | "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview", | 240 | "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview", |
241 | "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.", | 241 | "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.", |
242 | "is_article": "1", | 242 | "is_article": "1", |
243 | "is_index": "0", | ||
243 | "has_video": "0", | 244 | "has_video": "0", |
244 | "has_image": "0", | 245 | "has_image": "0", |
245 | "word_count": "3197" | 246 | "word_count": "3197" |
@@ -279,7 +280,7 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase | |||
279 | $res = $pocketImport->import(); | 280 | $res = $pocketImport->import(); |
280 | 281 | ||
281 | $this->assertTrue($res); | 282 | $this->assertTrue($res); |
282 | $this->assertEquals(['skipped' => 1, 'imported' => 1], $pocketImport->getSummary()); | 283 | $this->assertEquals(['skipped' => 1, 'imported' => 1, 'queued' => 0], $pocketImport->getSummary()); |
283 | } | 284 | } |
284 | 285 | ||
285 | /** | 286 | /** |
@@ -302,6 +303,11 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase | |||
302 | "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland", | 303 | "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland", |
303 | "favorite": "1", | 304 | "favorite": "1", |
304 | "status": "1", | 305 | "status": "1", |
306 | "time_added": "1473020899", | ||
307 | "time_updated": "1473020899", | ||
308 | "time_read": "0", | ||
309 | "time_favorited": "0", | ||
310 | "sort_id": 0, | ||
305 | "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.", | 311 | "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.", |
306 | "is_article": "1", | 312 | "is_article": "1", |
307 | "has_video": "1", | 313 | "has_video": "1", |
@@ -315,6 +321,11 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase | |||
315 | "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland", | 321 | "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland", |
316 | "favorite": "1", | 322 | "favorite": "1", |
317 | "status": "0", | 323 | "status": "0", |
324 | "time_added": "1473020899", | ||
325 | "time_updated": "1473020899", | ||
326 | "time_read": "0", | ||
327 | "time_favorited": "0", | ||
328 | "sort_id": 1, | ||
318 | "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.", | 329 | "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.", |
319 | "is_article": "1", | 330 | "is_article": "1", |
320 | "has_video": "0", | 331 | "has_video": "0", |
@@ -364,7 +375,174 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase | |||
364 | $res = $pocketImport->setMarkAsRead(true)->import(); | 375 | $res = $pocketImport->setMarkAsRead(true)->import(); |
365 | 376 | ||
366 | $this->assertTrue($res); | 377 | $this->assertTrue($res); |
367 | $this->assertEquals(['skipped' => 0, 'imported' => 2], $pocketImport->getSummary()); | 378 | $this->assertEquals(['skipped' => 0, 'imported' => 2, 'queued' => 0], $pocketImport->getSummary()); |
379 | } | ||
380 | |||
381 | /** | ||
382 | * Will sample results from https://getpocket.com/developer/docs/v3/retrieve. | ||
383 | */ | ||
384 | public function testImportWithRabbit() | ||
385 | { | ||
386 | $client = new Client(); | ||
387 | |||
388 | $body = <<<'JSON' | ||
389 | { | ||
390 | "item_id": "229279689", | ||
391 | "resolved_id": "229279689", | ||
392 | "given_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview", | ||
393 | "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland", | ||
394 | "favorite": "1", | ||
395 | "status": "1", | ||
396 | "time_added": "1473020899", | ||
397 | "time_updated": "1473020899", | ||
398 | "time_read": "0", | ||
399 | "time_favorited": "0", | ||
400 | "sort_id": 0, | ||
401 | "resolved_title": "The Massive Ryder Cup Preview", | ||
402 | "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview", | ||
403 | "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.", | ||
404 | "is_article": "1", | ||
405 | "has_video": "0", | ||
406 | "has_image": "0", | ||
407 | "word_count": "3197" | ||
408 | } | ||
409 | JSON; | ||
410 | |||
411 | $mock = new Mock([ | ||
412 | new Response(200, ['Content-Type' => 'application/json'], Stream::factory(json_encode(['access_token' => 'wunderbar_token']))), | ||
413 | new Response(200, ['Content-Type' => 'application/json'], Stream::factory(' | ||
414 | { | ||
415 | "status": 1, | ||
416 | "list": { | ||
417 | "229279690": '.$body.' | ||
418 | } | ||
419 | } | ||
420 | ')), | ||
421 | ]); | ||
422 | |||
423 | $client->getEmitter()->attach($mock); | ||
424 | |||
425 | $pocketImport = $this->getPocketImport(); | ||
426 | |||
427 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
428 | ->disableOriginalConstructor() | ||
429 | ->getMock(); | ||
430 | |||
431 | $entryRepo->expects($this->never()) | ||
432 | ->method('findByUrlAndUserId'); | ||
433 | |||
434 | $this->em | ||
435 | ->expects($this->never()) | ||
436 | ->method('getRepository'); | ||
437 | |||
438 | $entry = new Entry($this->user); | ||
439 | |||
440 | $this->contentProxy | ||
441 | ->expects($this->never()) | ||
442 | ->method('updateEntry'); | ||
443 | |||
444 | $producer = $this->getMockBuilder('OldSound\RabbitMqBundle\RabbitMq\Producer') | ||
445 | ->disableOriginalConstructor() | ||
446 | ->getMock(); | ||
447 | |||
448 | $bodyAsArray = json_decode($body, true); | ||
449 | // because with just use `new User()` so it doesn't have an id | ||
450 | $bodyAsArray['userId'] = null; | ||
451 | |||
452 | $producer | ||
453 | ->expects($this->once()) | ||
454 | ->method('publish') | ||
455 | ->with(json_encode($bodyAsArray)); | ||
456 | |||
457 | $pocketImport->setClient($client); | ||
458 | $pocketImport->setProducer($producer); | ||
459 | $pocketImport->authorize('wunderbar_code'); | ||
460 | |||
461 | $res = $pocketImport->setMarkAsRead(true)->import(); | ||
462 | |||
463 | $this->assertTrue($res); | ||
464 | $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 1], $pocketImport->getSummary()); | ||
465 | } | ||
466 | |||
467 | /** | ||
468 | * Will sample results from https://getpocket.com/developer/docs/v3/retrieve. | ||
469 | */ | ||
470 | public function testImportWithRedis() | ||
471 | { | ||
472 | $client = new Client(); | ||
473 | |||
474 | $body = <<<'JSON' | ||
475 | { | ||
476 | "item_id": "229279689", | ||
477 | "resolved_id": "229279689", | ||
478 | "given_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview", | ||
479 | "given_title": "The Massive Ryder Cup Preview - The Triangle Blog - Grantland", | ||
480 | "favorite": "1", | ||
481 | "status": "1", | ||
482 | "time_added": "1473020899", | ||
483 | "time_updated": "1473020899", | ||
484 | "time_read": "0", | ||
485 | "time_favorited": "0", | ||
486 | "sort_id": 0, | ||
487 | "resolved_title": "The Massive Ryder Cup Preview", | ||
488 | "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview", | ||
489 | "excerpt": "The list of things I love about the Ryder Cup is so long that it could fill a (tedious) novel, and golf fans can probably guess most of them.", | ||
490 | "is_article": "1", | ||
491 | "has_video": "0", | ||
492 | "has_image": "0", | ||
493 | "word_count": "3197" | ||
494 | } | ||
495 | JSON; | ||
496 | |||
497 | $mock = new Mock([ | ||
498 | new Response(200, ['Content-Type' => 'application/json'], Stream::factory(json_encode(['access_token' => 'wunderbar_token']))), | ||
499 | new Response(200, ['Content-Type' => 'application/json'], Stream::factory(' | ||
500 | { | ||
501 | "status": 1, | ||
502 | "list": { | ||
503 | "229279690": '.$body.' | ||
504 | } | ||
505 | } | ||
506 | ')), | ||
507 | ]); | ||
508 | |||
509 | $client->getEmitter()->attach($mock); | ||
510 | |||
511 | $pocketImport = $this->getPocketImport(); | ||
512 | |||
513 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
514 | ->disableOriginalConstructor() | ||
515 | ->getMock(); | ||
516 | |||
517 | $entryRepo->expects($this->never()) | ||
518 | ->method('findByUrlAndUserId'); | ||
519 | |||
520 | $this->em | ||
521 | ->expects($this->never()) | ||
522 | ->method('getRepository'); | ||
523 | |||
524 | $entry = new Entry($this->user); | ||
525 | |||
526 | $this->contentProxy | ||
527 | ->expects($this->never()) | ||
528 | ->method('updateEntry'); | ||
529 | |||
530 | $factory = new RedisMockFactory(); | ||
531 | $redisMock = $factory->getAdapter('Predis\Client', true); | ||
532 | |||
533 | $queue = new RedisQueue($redisMock, 'pocket'); | ||
534 | $producer = new Producer($queue); | ||
535 | |||
536 | $pocketImport->setClient($client); | ||
537 | $pocketImport->setProducer($producer); | ||
538 | $pocketImport->authorize('wunderbar_code'); | ||
539 | |||
540 | $res = $pocketImport->setMarkAsRead(true)->import(); | ||
541 | |||
542 | $this->assertTrue($res); | ||
543 | $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 1], $pocketImport->getSummary()); | ||
544 | |||
545 | $this->assertNotEmpty($redisMock->lpop('pocket')); | ||
368 | } | 546 | } |
369 | 547 | ||
370 | public function testImportBadResponse() | 548 | public function testImportBadResponse() |
@@ -402,6 +580,8 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase | |||
402 | "status": 1, | 580 | "status": 1, |
403 | "list": { | 581 | "list": { |
404 | "229279689": { | 582 | "229279689": { |
583 | "status": "1", | ||
584 | "favorite": "1", | ||
405 | "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview" | 585 | "resolved_url": "http://www.grantland.com/blog/the-triangle/post/_/id/38347/ryder-cup-preview" |
406 | } | 586 | } |
407 | } | 587 | } |
@@ -439,6 +619,6 @@ class PocketImportTest extends \PHPUnit_Framework_TestCase | |||
439 | $res = $pocketImport->import(); | 619 | $res = $pocketImport->import(); |
440 | 620 | ||
441 | $this->assertTrue($res); | 621 | $this->assertTrue($res); |
442 | $this->assertEquals(['skipped' => 1, 'imported' => 0], $pocketImport->getSummary()); | 622 | $this->assertEquals(['skipped' => 0, 'imported' => 1, 'queued' => 0], $pocketImport->getSummary()); |
443 | } | 623 | } |
444 | } | 624 | } |
diff --git a/tests/Wallabag/ImportBundle/Import/ReadabilityImportTest.php b/tests/Wallabag/ImportBundle/Import/ReadabilityImportTest.php new file mode 100644 index 00000000..d98cd486 --- /dev/null +++ b/tests/Wallabag/ImportBundle/Import/ReadabilityImportTest.php | |||
@@ -0,0 +1,233 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Tests\Wallabag\ImportBundle\Import; | ||
4 | |||
5 | use Wallabag\ImportBundle\Import\ReadabilityImport; | ||
6 | use Wallabag\UserBundle\Entity\User; | ||
7 | use Wallabag\CoreBundle\Entity\Entry; | ||
8 | use Wallabag\ImportBundle\Redis\Producer; | ||
9 | use Monolog\Logger; | ||
10 | use Monolog\Handler\TestHandler; | ||
11 | use Simpleue\Queue\RedisQueue; | ||
12 | use M6Web\Component\RedisMock\RedisMockFactory; | ||
13 | |||
14 | class ReadabilityImportTest extends \PHPUnit_Framework_TestCase | ||
15 | { | ||
16 | protected $user; | ||
17 | protected $em; | ||
18 | protected $logHandler; | ||
19 | protected $contentProxy; | ||
20 | |||
21 | private function getReadabilityImport($unsetUser = false) | ||
22 | { | ||
23 | $this->user = new User(); | ||
24 | |||
25 | $this->em = $this->getMockBuilder('Doctrine\ORM\EntityManager') | ||
26 | ->disableOriginalConstructor() | ||
27 | ->getMock(); | ||
28 | |||
29 | $this->contentProxy = $this->getMockBuilder('Wallabag\CoreBundle\Helper\ContentProxy') | ||
30 | ->disableOriginalConstructor() | ||
31 | ->getMock(); | ||
32 | |||
33 | $wallabag = new ReadabilityImport($this->em, $this->contentProxy); | ||
34 | |||
35 | $this->logHandler = new TestHandler(); | ||
36 | $logger = new Logger('test', [$this->logHandler]); | ||
37 | $wallabag->setLogger($logger); | ||
38 | |||
39 | if (false === $unsetUser) { | ||
40 | $wallabag->setUser($this->user); | ||
41 | } | ||
42 | |||
43 | return $wallabag; | ||
44 | } | ||
45 | |||
46 | public function testInit() | ||
47 | { | ||
48 | $readabilityImport = $this->getReadabilityImport(); | ||
49 | |||
50 | $this->assertEquals('Readability', $readabilityImport->getName()); | ||
51 | $this->assertNotEmpty($readabilityImport->getUrl()); | ||
52 | $this->assertEquals('import.readability.description', $readabilityImport->getDescription()); | ||
53 | } | ||
54 | |||
55 | public function testImport() | ||
56 | { | ||
57 | $readabilityImport = $this->getReadabilityImport(); | ||
58 | $readabilityImport->setFilepath(__DIR__.'/../fixtures/readability.json'); | ||
59 | |||
60 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
61 | ->disableOriginalConstructor() | ||
62 | ->getMock(); | ||
63 | |||
64 | $entryRepo->expects($this->exactly(24)) | ||
65 | ->method('findByUrlAndUserId') | ||
66 | ->willReturn(false); | ||
67 | |||
68 | $this->em | ||
69 | ->expects($this->any()) | ||
70 | ->method('getRepository') | ||
71 | ->willReturn($entryRepo); | ||
72 | |||
73 | $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry') | ||
74 | ->disableOriginalConstructor() | ||
75 | ->getMock(); | ||
76 | |||
77 | $this->contentProxy | ||
78 | ->expects($this->exactly(24)) | ||
79 | ->method('updateEntry') | ||
80 | ->willReturn($entry); | ||
81 | |||
82 | $res = $readabilityImport->import(); | ||
83 | |||
84 | $this->assertTrue($res); | ||
85 | $this->assertEquals(['skipped' => 0, 'imported' => 24, 'queued' => 0], $readabilityImport->getSummary()); | ||
86 | } | ||
87 | |||
88 | public function testImportAndMarkAllAsRead() | ||
89 | { | ||
90 | $readabilityImport = $this->getReadabilityImport(); | ||
91 | $readabilityImport->setFilepath(__DIR__.'/../fixtures/readability-read.json'); | ||
92 | |||
93 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
94 | ->disableOriginalConstructor() | ||
95 | ->getMock(); | ||
96 | |||
97 | $entryRepo->expects($this->exactly(2)) | ||
98 | ->method('findByUrlAndUserId') | ||
99 | ->will($this->onConsecutiveCalls(false, true)); | ||
100 | |||
101 | $this->em | ||
102 | ->expects($this->any()) | ||
103 | ->method('getRepository') | ||
104 | ->willReturn($entryRepo); | ||
105 | |||
106 | $this->contentProxy | ||
107 | ->expects($this->exactly(1)) | ||
108 | ->method('updateEntry') | ||
109 | ->willReturn(new Entry($this->user)); | ||
110 | |||
111 | // check that every entry persisted are archived | ||
112 | $this->em | ||
113 | ->expects($this->any()) | ||
114 | ->method('persist') | ||
115 | ->with($this->callback(function ($persistedEntry) { | ||
116 | return $persistedEntry->isArchived(); | ||
117 | })); | ||
118 | |||
119 | $res = $readabilityImport->setMarkAsRead(true)->import(); | ||
120 | |||
121 | $this->assertTrue($res); | ||
122 | |||
123 | $this->assertEquals(['skipped' => 1, 'imported' => 1, 'queued' => 0], $readabilityImport->getSummary()); | ||
124 | } | ||
125 | |||
126 | public function testImportWithRabbit() | ||
127 | { | ||
128 | $readabilityImport = $this->getReadabilityImport(); | ||
129 | $readabilityImport->setFilepath(__DIR__.'/../fixtures/readability.json'); | ||
130 | |||
131 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
132 | ->disableOriginalConstructor() | ||
133 | ->getMock(); | ||
134 | |||
135 | $entryRepo->expects($this->never()) | ||
136 | ->method('findByUrlAndUserId'); | ||
137 | |||
138 | $this->em | ||
139 | ->expects($this->never()) | ||
140 | ->method('getRepository'); | ||
141 | |||
142 | $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry') | ||
143 | ->disableOriginalConstructor() | ||
144 | ->getMock(); | ||
145 | |||
146 | $this->contentProxy | ||
147 | ->expects($this->never()) | ||
148 | ->method('updateEntry'); | ||
149 | |||
150 | $producer = $this->getMockBuilder('OldSound\RabbitMqBundle\RabbitMq\Producer') | ||
151 | ->disableOriginalConstructor() | ||
152 | ->getMock(); | ||
153 | |||
154 | $producer | ||
155 | ->expects($this->exactly(24)) | ||
156 | ->method('publish'); | ||
157 | |||
158 | $readabilityImport->setProducer($producer); | ||
159 | |||
160 | $res = $readabilityImport->setMarkAsRead(true)->import(); | ||
161 | |||
162 | $this->assertTrue($res); | ||
163 | $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 24], $readabilityImport->getSummary()); | ||
164 | } | ||
165 | |||
166 | public function testImportWithRedis() | ||
167 | { | ||
168 | $readabilityImport = $this->getReadabilityImport(); | ||
169 | $readabilityImport->setFilepath(__DIR__.'/../fixtures/readability.json'); | ||
170 | |||
171 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
172 | ->disableOriginalConstructor() | ||
173 | ->getMock(); | ||
174 | |||
175 | $entryRepo->expects($this->never()) | ||
176 | ->method('findByUrlAndUserId'); | ||
177 | |||
178 | $this->em | ||
179 | ->expects($this->never()) | ||
180 | ->method('getRepository'); | ||
181 | |||
182 | $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry') | ||
183 | ->disableOriginalConstructor() | ||
184 | ->getMock(); | ||
185 | |||
186 | $this->contentProxy | ||
187 | ->expects($this->never()) | ||
188 | ->method('updateEntry'); | ||
189 | |||
190 | $factory = new RedisMockFactory(); | ||
191 | $redisMock = $factory->getAdapter('Predis\Client', true); | ||
192 | |||
193 | $queue = new RedisQueue($redisMock, 'readability'); | ||
194 | $producer = new Producer($queue); | ||
195 | |||
196 | $readabilityImport->setProducer($producer); | ||
197 | |||
198 | $res = $readabilityImport->setMarkAsRead(true)->import(); | ||
199 | |||
200 | $this->assertTrue($res); | ||
201 | $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 24], $readabilityImport->getSummary()); | ||
202 | |||
203 | $this->assertNotEmpty($redisMock->lpop('readability')); | ||
204 | } | ||
205 | |||
206 | public function testImportBadFile() | ||
207 | { | ||
208 | $readabilityImport = $this->getReadabilityImport(); | ||
209 | $readabilityImport->setFilepath(__DIR__.'/../fixtures/wallabag-v1.jsonx'); | ||
210 | |||
211 | $res = $readabilityImport->import(); | ||
212 | |||
213 | $this->assertFalse($res); | ||
214 | |||
215 | $records = $this->logHandler->getRecords(); | ||
216 | $this->assertContains('ReadabilityImport: unable to read file', $records[0]['message']); | ||
217 | $this->assertEquals('ERROR', $records[0]['level_name']); | ||
218 | } | ||
219 | |||
220 | public function testImportUserNotDefined() | ||
221 | { | ||
222 | $readabilityImport = $this->getReadabilityImport(true); | ||
223 | $readabilityImport->setFilepath(__DIR__.'/../fixtures/readability.json'); | ||
224 | |||
225 | $res = $readabilityImport->import(); | ||
226 | |||
227 | $this->assertFalse($res); | ||
228 | |||
229 | $records = $this->logHandler->getRecords(); | ||
230 | $this->assertContains('ReadabilityImport: user is not defined', $records[0]['message']); | ||
231 | $this->assertEquals('ERROR', $records[0]['level_name']); | ||
232 | } | ||
233 | } | ||
diff --git a/tests/Wallabag/ImportBundle/Import/WallabagV1ImportTest.php b/tests/Wallabag/ImportBundle/Import/WallabagV1ImportTest.php index bdc47dac..82dc4c7e 100644 --- a/tests/Wallabag/ImportBundle/Import/WallabagV1ImportTest.php +++ b/tests/Wallabag/ImportBundle/Import/WallabagV1ImportTest.php | |||
@@ -5,8 +5,11 @@ namespace Tests\Wallabag\ImportBundle\Import; | |||
5 | use Wallabag\ImportBundle\Import\WallabagV1Import; | 5 | use Wallabag\ImportBundle\Import\WallabagV1Import; |
6 | use Wallabag\UserBundle\Entity\User; | 6 | use Wallabag\UserBundle\Entity\User; |
7 | use Wallabag\CoreBundle\Entity\Entry; | 7 | use Wallabag\CoreBundle\Entity\Entry; |
8 | use Wallabag\ImportBundle\Redis\Producer; | ||
8 | use Monolog\Logger; | 9 | use Monolog\Logger; |
9 | use Monolog\Handler\TestHandler; | 10 | use Monolog\Handler\TestHandler; |
11 | use Simpleue\Queue\RedisQueue; | ||
12 | use M6Web\Component\RedisMock\RedisMockFactory; | ||
10 | 13 | ||
11 | class WallabagV1ImportTest extends \PHPUnit_Framework_TestCase | 14 | class WallabagV1ImportTest extends \PHPUnit_Framework_TestCase |
12 | { | 15 | { |
@@ -23,6 +26,20 @@ class WallabagV1ImportTest extends \PHPUnit_Framework_TestCase | |||
23 | ->disableOriginalConstructor() | 26 | ->disableOriginalConstructor() |
24 | ->getMock(); | 27 | ->getMock(); |
25 | 28 | ||
29 | $this->uow = $this->getMockBuilder('Doctrine\ORM\UnitOfWork') | ||
30 | ->disableOriginalConstructor() | ||
31 | ->getMock(); | ||
32 | |||
33 | $this->em | ||
34 | ->expects($this->any()) | ||
35 | ->method('getUnitOfWork') | ||
36 | ->willReturn($this->uow); | ||
37 | |||
38 | $this->uow | ||
39 | ->expects($this->any()) | ||
40 | ->method('getScheduledEntityInsertions') | ||
41 | ->willReturn([]); | ||
42 | |||
26 | $this->contentProxy = $this->getMockBuilder('Wallabag\CoreBundle\Helper\ContentProxy') | 43 | $this->contentProxy = $this->getMockBuilder('Wallabag\CoreBundle\Helper\ContentProxy') |
27 | ->disableOriginalConstructor() | 44 | ->disableOriginalConstructor() |
28 | ->getMock(); | 45 | ->getMock(); |
@@ -79,7 +96,7 @@ class WallabagV1ImportTest extends \PHPUnit_Framework_TestCase | |||
79 | $res = $wallabagV1Import->import(); | 96 | $res = $wallabagV1Import->import(); |
80 | 97 | ||
81 | $this->assertTrue($res); | 98 | $this->assertTrue($res); |
82 | $this->assertEquals(['skipped' => 1, 'imported' => 3], $wallabagV1Import->getSummary()); | 99 | $this->assertEquals(['skipped' => 1, 'imported' => 3, 'queued' => 0], $wallabagV1Import->getSummary()); |
83 | } | 100 | } |
84 | 101 | ||
85 | public function testImportAndMarkAllAsRead() | 102 | public function testImportAndMarkAllAsRead() |
@@ -117,7 +134,87 @@ class WallabagV1ImportTest extends \PHPUnit_Framework_TestCase | |||
117 | 134 | ||
118 | $this->assertTrue($res); | 135 | $this->assertTrue($res); |
119 | 136 | ||
120 | $this->assertEquals(['skipped' => 0, 'imported' => 3], $wallabagV1Import->getSummary()); | 137 | $this->assertEquals(['skipped' => 0, 'imported' => 3, 'queued' => 0], $wallabagV1Import->getSummary()); |
138 | } | ||
139 | |||
140 | public function testImportWithRabbit() | ||
141 | { | ||
142 | $wallabagV1Import = $this->getWallabagV1Import(); | ||
143 | $wallabagV1Import->setFilepath(__DIR__.'/../fixtures/wallabag-v1.json'); | ||
144 | |||
145 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
146 | ->disableOriginalConstructor() | ||
147 | ->getMock(); | ||
148 | |||
149 | $entryRepo->expects($this->never()) | ||
150 | ->method('findByUrlAndUserId'); | ||
151 | |||
152 | $this->em | ||
153 | ->expects($this->never()) | ||
154 | ->method('getRepository'); | ||
155 | |||
156 | $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry') | ||
157 | ->disableOriginalConstructor() | ||
158 | ->getMock(); | ||
159 | |||
160 | $this->contentProxy | ||
161 | ->expects($this->never()) | ||
162 | ->method('updateEntry'); | ||
163 | |||
164 | $producer = $this->getMockBuilder('OldSound\RabbitMqBundle\RabbitMq\Producer') | ||
165 | ->disableOriginalConstructor() | ||
166 | ->getMock(); | ||
167 | |||
168 | $producer | ||
169 | ->expects($this->exactly(4)) | ||
170 | ->method('publish'); | ||
171 | |||
172 | $wallabagV1Import->setProducer($producer); | ||
173 | |||
174 | $res = $wallabagV1Import->setMarkAsRead(true)->import(); | ||
175 | |||
176 | $this->assertTrue($res); | ||
177 | $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 4], $wallabagV1Import->getSummary()); | ||
178 | } | ||
179 | |||
180 | public function testImportWithRedis() | ||
181 | { | ||
182 | $wallabagV1Import = $this->getWallabagV1Import(); | ||
183 | $wallabagV1Import->setFilepath(__DIR__.'/../fixtures/wallabag-v1.json'); | ||
184 | |||
185 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
186 | ->disableOriginalConstructor() | ||
187 | ->getMock(); | ||
188 | |||
189 | $entryRepo->expects($this->never()) | ||
190 | ->method('findByUrlAndUserId'); | ||
191 | |||
192 | $this->em | ||
193 | ->expects($this->never()) | ||
194 | ->method('getRepository'); | ||
195 | |||
196 | $entry = $this->getMockBuilder('Wallabag\CoreBundle\Entity\Entry') | ||
197 | ->disableOriginalConstructor() | ||
198 | ->getMock(); | ||
199 | |||
200 | $this->contentProxy | ||
201 | ->expects($this->never()) | ||
202 | ->method('updateEntry'); | ||
203 | |||
204 | $factory = new RedisMockFactory(); | ||
205 | $redisMock = $factory->getAdapter('Predis\Client', true); | ||
206 | |||
207 | $queue = new RedisQueue($redisMock, 'wallabag_v1'); | ||
208 | $producer = new Producer($queue); | ||
209 | |||
210 | $wallabagV1Import->setProducer($producer); | ||
211 | |||
212 | $res = $wallabagV1Import->setMarkAsRead(true)->import(); | ||
213 | |||
214 | $this->assertTrue($res); | ||
215 | $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 4], $wallabagV1Import->getSummary()); | ||
216 | |||
217 | $this->assertNotEmpty($redisMock->lpop('wallabag_v1')); | ||
121 | } | 218 | } |
122 | 219 | ||
123 | public function testImportBadFile() | 220 | public function testImportBadFile() |
diff --git a/tests/Wallabag/ImportBundle/Import/WallabagV2ImportTest.php b/tests/Wallabag/ImportBundle/Import/WallabagV2ImportTest.php index 4a45e0f0..bea89efb 100644 --- a/tests/Wallabag/ImportBundle/Import/WallabagV2ImportTest.php +++ b/tests/Wallabag/ImportBundle/Import/WallabagV2ImportTest.php | |||
@@ -5,8 +5,11 @@ namespace Tests\Wallabag\ImportBundle\Import; | |||
5 | use Wallabag\ImportBundle\Import\WallabagV2Import; | 5 | use Wallabag\ImportBundle\Import\WallabagV2Import; |
6 | use Wallabag\UserBundle\Entity\User; | 6 | use Wallabag\UserBundle\Entity\User; |
7 | use Wallabag\CoreBundle\Entity\Entry; | 7 | use Wallabag\CoreBundle\Entity\Entry; |
8 | use Wallabag\ImportBundle\Redis\Producer; | ||
8 | use Monolog\Logger; | 9 | use Monolog\Logger; |
9 | use Monolog\Handler\TestHandler; | 10 | use Monolog\Handler\TestHandler; |
11 | use Simpleue\Queue\RedisQueue; | ||
12 | use M6Web\Component\RedisMock\RedisMockFactory; | ||
10 | 13 | ||
11 | class WallabagV2ImportTest extends \PHPUnit_Framework_TestCase | 14 | class WallabagV2ImportTest extends \PHPUnit_Framework_TestCase |
12 | { | 15 | { |
@@ -23,6 +26,20 @@ class WallabagV2ImportTest extends \PHPUnit_Framework_TestCase | |||
23 | ->disableOriginalConstructor() | 26 | ->disableOriginalConstructor() |
24 | ->getMock(); | 27 | ->getMock(); |
25 | 28 | ||
29 | $this->uow = $this->getMockBuilder('Doctrine\ORM\UnitOfWork') | ||
30 | ->disableOriginalConstructor() | ||
31 | ->getMock(); | ||
32 | |||
33 | $this->em | ||
34 | ->expects($this->any()) | ||
35 | ->method('getUnitOfWork') | ||
36 | ->willReturn($this->uow); | ||
37 | |||
38 | $this->uow | ||
39 | ->expects($this->any()) | ||
40 | ->method('getScheduledEntityInsertions') | ||
41 | ->willReturn([]); | ||
42 | |||
26 | $this->contentProxy = $this->getMockBuilder('Wallabag\CoreBundle\Helper\ContentProxy') | 43 | $this->contentProxy = $this->getMockBuilder('Wallabag\CoreBundle\Helper\ContentProxy') |
27 | ->disableOriginalConstructor() | 44 | ->disableOriginalConstructor() |
28 | ->getMock(); | 45 | ->getMock(); |
@@ -75,7 +92,7 @@ class WallabagV2ImportTest extends \PHPUnit_Framework_TestCase | |||
75 | $res = $wallabagV2Import->import(); | 92 | $res = $wallabagV2Import->import(); |
76 | 93 | ||
77 | $this->assertTrue($res); | 94 | $this->assertTrue($res); |
78 | $this->assertEquals(['skipped' => 22, 'imported' => 2], $wallabagV2Import->getSummary()); | 95 | $this->assertEquals(['skipped' => 22, 'imported' => 2, 'queued' => 0], $wallabagV2Import->getSummary()); |
79 | } | 96 | } |
80 | 97 | ||
81 | public function testImportAndMarkAllAsRead() | 98 | public function testImportAndMarkAllAsRead() |
@@ -113,7 +130,79 @@ class WallabagV2ImportTest extends \PHPUnit_Framework_TestCase | |||
113 | 130 | ||
114 | $this->assertTrue($res); | 131 | $this->assertTrue($res); |
115 | 132 | ||
116 | $this->assertEquals(['skipped' => 0, 'imported' => 2], $wallabagV2Import->getSummary()); | 133 | $this->assertEquals(['skipped' => 0, 'imported' => 2, 'queued' => 0], $wallabagV2Import->getSummary()); |
134 | } | ||
135 | |||
136 | public function testImportWithRabbit() | ||
137 | { | ||
138 | $wallabagV2Import = $this->getWallabagV2Import(); | ||
139 | $wallabagV2Import->setFilepath(__DIR__.'/../fixtures/wallabag-v2.json'); | ||
140 | |||
141 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
142 | ->disableOriginalConstructor() | ||
143 | ->getMock(); | ||
144 | |||
145 | $entryRepo->expects($this->never()) | ||
146 | ->method('findByUrlAndUserId'); | ||
147 | |||
148 | $this->em | ||
149 | ->expects($this->never()) | ||
150 | ->method('getRepository'); | ||
151 | |||
152 | $this->contentProxy | ||
153 | ->expects($this->never()) | ||
154 | ->method('updateEntry'); | ||
155 | |||
156 | $producer = $this->getMockBuilder('OldSound\RabbitMqBundle\RabbitMq\Producer') | ||
157 | ->disableOriginalConstructor() | ||
158 | ->getMock(); | ||
159 | |||
160 | $producer | ||
161 | ->expects($this->exactly(24)) | ||
162 | ->method('publish'); | ||
163 | |||
164 | $wallabagV2Import->setProducer($producer); | ||
165 | |||
166 | $res = $wallabagV2Import->setMarkAsRead(true)->import(); | ||
167 | |||
168 | $this->assertTrue($res); | ||
169 | $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 24], $wallabagV2Import->getSummary()); | ||
170 | } | ||
171 | |||
172 | public function testImportWithRedis() | ||
173 | { | ||
174 | $wallabagV2Import = $this->getWallabagV2Import(); | ||
175 | $wallabagV2Import->setFilepath(__DIR__.'/../fixtures/wallabag-v2.json'); | ||
176 | |||
177 | $entryRepo = $this->getMockBuilder('Wallabag\CoreBundle\Repository\EntryRepository') | ||
178 | ->disableOriginalConstructor() | ||
179 | ->getMock(); | ||
180 | |||
181 | $entryRepo->expects($this->never()) | ||
182 | ->method('findByUrlAndUserId'); | ||
183 | |||
184 | $this->em | ||
185 | ->expects($this->never()) | ||
186 | ->method('getRepository'); | ||
187 | |||
188 | $this->contentProxy | ||
189 | ->expects($this->never()) | ||
190 | ->method('updateEntry'); | ||
191 | |||
192 | $factory = new RedisMockFactory(); | ||
193 | $redisMock = $factory->getAdapter('Predis\Client', true); | ||
194 | |||
195 | $queue = new RedisQueue($redisMock, 'wallabag_v2'); | ||
196 | $producer = new Producer($queue); | ||
197 | |||
198 | $wallabagV2Import->setProducer($producer); | ||
199 | |||
200 | $res = $wallabagV2Import->setMarkAsRead(true)->import(); | ||
201 | |||
202 | $this->assertTrue($res); | ||
203 | $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 24], $wallabagV2Import->getSummary()); | ||
204 | |||
205 | $this->assertNotEmpty($redisMock->lpop('wallabag_v2')); | ||
117 | } | 206 | } |
118 | 207 | ||
119 | public function testImportBadFile() | 208 | public function testImportBadFile() |
@@ -152,7 +241,7 @@ class WallabagV2ImportTest extends \PHPUnit_Framework_TestCase | |||
152 | $res = $wallabagV2Import->import(); | 241 | $res = $wallabagV2Import->import(); |
153 | 242 | ||
154 | $this->assertFalse($res); | 243 | $this->assertFalse($res); |
155 | $this->assertEquals(['skipped' => 0, 'imported' => 0], $wallabagV2Import->getSummary()); | 244 | $this->assertEquals(['skipped' => 0, 'imported' => 0, 'queued' => 0], $wallabagV2Import->getSummary()); |
156 | } | 245 | } |
157 | 246 | ||
158 | public function testImportWithExceptionFromGraby() | 247 | public function testImportWithExceptionFromGraby() |
@@ -181,6 +270,6 @@ class WallabagV2ImportTest extends \PHPUnit_Framework_TestCase | |||
181 | $res = $wallabagV2Import->import(); | 270 | $res = $wallabagV2Import->import(); |
182 | 271 | ||
183 | $this->assertTrue($res); | 272 | $this->assertTrue($res); |
184 | $this->assertEquals(['skipped' => 24, 'imported' => 0], $wallabagV2Import->getSummary()); | 273 | $this->assertEquals(['skipped' => 22, 'imported' => 2, 'queued' => 0], $wallabagV2Import->getSummary()); |
185 | } | 274 | } |
186 | } | 275 | } |
diff --git a/tests/Wallabag/ImportBundle/fixtures/chrome-bookmarks b/tests/Wallabag/ImportBundle/fixtures/chrome-bookmarks new file mode 100644 index 00000000..0478eb41 --- /dev/null +++ b/tests/Wallabag/ImportBundle/fixtures/chrome-bookmarks | |||
@@ -0,0 +1,36 @@ | |||
1 | { | ||
2 | "checksum": "f3aa0e9c0edad632a246f7e98ec64918", | ||
3 | "roots": { | ||
4 | "bookmark_bar": { | ||
5 | "children": [ { | ||
6 | "date_added": "13118850929335823", | ||
7 | "id": "6", | ||
8 | "name": "\"La multiplication des chefs de projet est une catastrophe managériale majeure\", affirme le sociologue François Dupuy - Ressources humaines", | ||
9 | "type": "url", | ||
10 | "url": "http://www.usinenouvelle.com/article/la-multiplication-des-chefs-de-projet-est-une-catastrophe-manageriale-majeure-affirme-le-sociologue-francois-dupuy.N307730" | ||
11 | } ], | ||
12 | "date_added": "13118829474385693", | ||
13 | "date_modified": "13118850929335823", | ||
14 | "id": "1", | ||
15 | "name": "Barre de favoris", | ||
16 | "type": "folder" | ||
17 | }, | ||
18 | "other": { | ||
19 | "children": [ ], | ||
20 | "date_added": "13118829474385701", | ||
21 | "date_modified": "0", | ||
22 | "id": "2", | ||
23 | "name": "Autres favoris", | ||
24 | "type": "folder" | ||
25 | }, | ||
26 | "synced": { | ||
27 | "children": [ ], | ||
28 | "date_added": "13118829474385702", | ||
29 | "date_modified": "0", | ||
30 | "id": "3", | ||
31 | "name": "Favoris sur mobile", | ||
32 | "type": "folder" | ||
33 | } | ||
34 | }, | ||
35 | "version": 1 | ||
36 | } | ||
diff --git a/tests/Wallabag/ImportBundle/fixtures/firefox-bookmarks.json b/tests/Wallabag/ImportBundle/fixtures/firefox-bookmarks.json new file mode 100644 index 00000000..406b5697 --- /dev/null +++ b/tests/Wallabag/ImportBundle/fixtures/firefox-bookmarks.json | |||
@@ -0,0 +1,63 @@ | |||
1 | { | ||
2 | "guid": "root________", | ||
3 | "title": "", | ||
4 | "index": 0, | ||
5 | "dateAdded": 1388166091504000, | ||
6 | "lastModified": 1472897622350000, | ||
7 | "id": 1, | ||
8 | "type": "text/x-moz-place-container", | ||
9 | "root": "placesRoot", | ||
10 | "children": [ | ||
11 | { | ||
12 | "guid": "toolbar_____", | ||
13 | "title": "Barre personnelle", | ||
14 | "index": 1, | ||
15 | "dateAdded": 1388166091504000, | ||
16 | "lastModified": 1472897622263000, | ||
17 | "id": 3, | ||
18 | "annos": [ | ||
19 | { | ||
20 | "name": "bookmarkProperties/description", | ||
21 | "flags": 0, | ||
22 | "expires": 4, | ||
23 | "value": "Ajoutez des marque-pages dans ce dossier pour les voir apparaître sur votre barre personnelle" | ||
24 | } | ||
25 | ], | ||
26 | "type": "text/x-moz-place-container", | ||
27 | "root": "toolbarFolder", | ||
28 | "children": [ | ||
29 | { | ||
30 | "guid": "tard77lzbC5H", | ||
31 | "title": "Orange offre un meilleur réseau mobile que Bouygues et SFR, Free derrière - L'Express L'Expansion", | ||
32 | "index": 1, | ||
33 | "dateAdded": 1388166091644000, | ||
34 | "lastModified": 1388166091644000, | ||
35 | "tags":"test,tag", | ||
36 | "id": 4, | ||
37 | "type": "text/x-moz-place", | ||
38 | "uri": "http://lexpansion.lexpress.fr/high-tech/orange-offre-un-meilleur-reseau-mobile-que-bouygues-et-sfr-free-derriere_1811554.html" | ||
39 | }, | ||
40 | { | ||
41 | "guid": "E385l9vZ_LVn", | ||
42 | "title": "Parser for Exported Bookmarks HTML file of Google Chrome and Mozilla in Java", | ||
43 | "index": 1, | ||
44 | "dateAdded": 1388166091544000, | ||
45 | "lastModified": 1388166091545000, | ||
46 | "id": 5, | ||
47 | "type": "text/x-moz-place", | ||
48 | "uri": "http://stackoverflow.com/questions/15017163/parser-for-exported-bookmarks-html-file-of-google-chrome-and-mozilla-in-java" | ||
49 | } | ||
50 | ] | ||
51 | }, | ||
52 | { | ||
53 | "guid": "unfiled_____", | ||
54 | "title": "Autres marque-pages", | ||
55 | "index": 3, | ||
56 | "dateAdded": 1388166091504000, | ||
57 | "lastModified": 1388166091542000, | ||
58 | "id": 6, | ||
59 | "type": "text/x-moz-place-container", | ||
60 | "root": "unfiledBookmarksFolder" | ||
61 | } | ||
62 | ] | ||
63 | } | ||
diff --git a/tests/Wallabag/ImportBundle/fixtures/instapaper-export.csv b/tests/Wallabag/ImportBundle/fixtures/instapaper-export.csv new file mode 100644 index 00000000..28a4c8e6 --- /dev/null +++ b/tests/Wallabag/ImportBundle/fixtures/instapaper-export.csv | |||
@@ -0,0 +1,4 @@ | |||
1 | URL,Title,Selection,Folder | ||
2 | http://www.liberation.fr/societe/2012/12/06/baumettes-un-tour-en-cellule_865551,Baumettes : un tour en cellule,,Unread | ||
3 | https://redditblog.com/2016/09/20/amp-and-reactredux/,AMP and React+Redux: Why Not?,,Archive | ||
4 | https://medium.com/@the_minh/why-foursquare-swarm-is-still-my-favourite-social-network-e38228493e6c,Why Foursquare / Swarm is still my favourite social network,,Starred | ||
diff --git a/tests/Wallabag/ImportBundle/fixtures/readability-read.json b/tests/Wallabag/ImportBundle/fixtures/readability-read.json new file mode 100644 index 00000000..c60767dc --- /dev/null +++ b/tests/Wallabag/ImportBundle/fixtures/readability-read.json | |||
@@ -0,0 +1,25 @@ | |||
1 | { | ||
2 | "bookmarks": [ | ||
3 | { | ||
4 | "article__excerpt": "This is a guest post from Moritz Beller from the Delft University of Technology in The Netherlands. His team produced amazing research on several million Travis CI builds, creating invaluable…", | ||
5 | "favorite": false, | ||
6 | "date_archived": "2016-08-02T06:49:30", | ||
7 | "article__url": "https://blog.travis-ci.com/2016-07-28-what-we-learned-from-analyzing-2-million-travis-builds/", | ||
8 | "date_added": "2016-08-01T05:24:16", | ||
9 | "date_favorited": null, | ||
10 | "article__title": "Travis", | ||
11 | "archive": true | ||
12 | }, | ||
13 | { | ||
14 | "article__excerpt": "The GraphQL Type system describes the capabilities of a GraphQL server and is used to determine if a query is valid. The type system also describes the input types of query variables to determine if…", | ||
15 | "favorite": false, | ||
16 | "date_archived": "2016-07-19T06:48:31", | ||
17 | "article__url": "https://facebook.github.io/graphql/", | ||
18 | "date_added": "2016-06-24T17:50:16", | ||
19 | "date_favorited": null, | ||
20 | "article__title": "GraphQL", | ||
21 | "archive": true | ||
22 | } | ||
23 | ], | ||
24 | "recommendations": [] | ||
25 | } | ||
diff --git a/tests/Wallabag/ImportBundle/fixtures/readability.json b/tests/Wallabag/ImportBundle/fixtures/readability.json new file mode 100644 index 00000000..32f6fa53 --- /dev/null +++ b/tests/Wallabag/ImportBundle/fixtures/readability.json | |||
@@ -0,0 +1,176 @@ | |||
1 | { | ||
2 | "bookmarks": [ | ||
3 | { | ||
4 | "article__excerpt": "When Twitter started it had so much promise to change the way we communicate. But now it has been ruined by the amount of garbage and hate we have to wade through. It’s like that polluted…", | ||
5 | "favorite": false, | ||
6 | "date_archived": null, | ||
7 | "article__url": "https://venngage.com/blog/hashtags-are-worthless/", | ||
8 | "date_added": "2016-08-25T12:05:00", | ||
9 | "date_favorited": null, | ||
10 | "article__title": "We Looked At 167,943 Tweets & Found Out Hashtags Are Worthless", | ||
11 | "archive": false | ||
12 | }, | ||
13 | { | ||
14 | "article__title": "Réfugiés: l'UE va créer 100 000 places d'accueil dans les Balkans", | ||
15 | "article__url": "http://www.liberation.fr/planete/2015/10/26/refugies-l-ue-va-creer-100-000-places-d-accueil-dans-les-balkans_1408867", | ||
16 | "archive": false, | ||
17 | "date_added": "2016-09-08T11:55:58+0200", | ||
18 | "favorite": false | ||
19 | }, | ||
20 | { | ||
21 | "article__title": "No title found", | ||
22 | "article__url": "http://news.nationalgeographic.com/2016/02/160211-albatrosses-mothers-babies-animals-science/&sf20739758=1", | ||
23 | "archive": false, | ||
24 | "date_added": "2016-09-08T11:55:58+0200", | ||
25 | "favorite": true | ||
26 | }, | ||
27 | { | ||
28 | "archive": 0, | ||
29 | "date_added": "2016-09-08T11:55:58+0200", | ||
30 | "favorite": 0, | ||
31 | "article__title": "Échecs", | ||
32 | "article__url": "https://fr.wikipedia.org/wiki/Échecs" | ||
33 | }, | ||
34 | { | ||
35 | "archive": 0, | ||
36 | "date_added": "2016-09-08T11:55:58+0200", | ||
37 | "favorite": 0, | ||
38 | "article__title": "90% des dossiers médicaux des Coréens du sud vendus à des entreprises privées - ZATAZ", | ||
39 | "article__url": "http://www.zataz.com/90-des-dossiers-medicaux-des-coreens-du-sud-vendus-a-des-entreprises-privees/" | ||
40 | }, | ||
41 | { | ||
42 | "archive": 0, | ||
43 | "date_added": "2016-09-08T11:55:58+0200", | ||
44 | "favorite": 0, | ||
45 | "article__title": "Mass Surveillance As Art", | ||
46 | "article__url": "https://www.nationaljournal.com/s/73311/mass-surveillance-art" | ||
47 | }, | ||
48 | { | ||
49 | "archive": 0, | ||
50 | "date_added": "2016-09-08T11:55:58+0200", | ||
51 | "favorite": 0, | ||
52 | "article__title": "What David Cameron did to the pig, his party is now doing to the country", | ||
53 | "article__url": "http://www.newstatesman.com/2015/09/what-david-cameron-did-pig-his-party-now-doing-country" | ||
54 | }, | ||
55 | { | ||
56 | "archive": 1, | ||
57 | "date_added": "2016-09-08T11:55:58+0200", | ||
58 | "favorite": 0, | ||
59 | "article__title": "CLICK HERE to support 2016 CES Winner, Revolutionary Auto-Tracking Robot", | ||
60 | "article__url": "https://www.indiegogo.com/projects/2016-ces-winner-revolutionary-auto-tracking-robot" | ||
61 | }, | ||
62 | { | ||
63 | "archive": 0, | ||
64 | "date_added": "2016-09-08T11:55:58+0200", | ||
65 | "favorite": 1, | ||
66 | "article__title": "No title found", | ||
67 | "article__url": "http://carnetdevol.shost.ca/wordpress/aide-memoire-sur-les-commandes-associees-a-systemd/" | ||
68 | }, | ||
69 | { | ||
70 | "archive": 1, | ||
71 | "date_added": "2016-09-08T11:55:58+0200", | ||
72 | "favorite": 0, | ||
73 | "article__title": "Présentation d'Arduino - Tuto Arduino - Le blog d'Eskimon", | ||
74 | "article__url": "http://eskimon.fr/73-arduino-101-presentation" | ||
75 | }, | ||
76 | { | ||
77 | "archive": 1, | ||
78 | "date_added": "2016-09-08T11:55:58+0200", | ||
79 | "favorite": 0, | ||
80 | "article__title": "Lenovo ThinkPad X1 Carbon Ultrabook Review", | ||
81 | "article__url": "http://www.notebookcheck.net/Lenovo-ThinkPad-X1-Carbon-Ultrabook-Review.138033.0.html" | ||
82 | }, | ||
83 | { | ||
84 | "archive": 0, | ||
85 | "date_added": "2016-09-08T11:55:58+0200", | ||
86 | "favorite": 0, | ||
87 | "article__title": "Visitons le Château de Landsberg !", | ||
88 | "article__url": "http://autour-du-mont-sainte-odile.overblog.com/2016/01/visitons-le-chateau-de-landsberg.html" | ||
89 | }, | ||
90 | { | ||
91 | "archive": 1, | ||
92 | "date_added": "2016-09-08T11:55:58+0200", | ||
93 | "favorite": 0, | ||
94 | "article__title": "Contrer les stéréotypes par les livres : “C'est dès l'enfance qu'ils se construisent”", | ||
95 | "article__url": "https://www.actualitte.com/article/monde-edition/contrer-les-stereotypes-par-les-livres-c-est-des-l-enfance-qu-ils-se-construisent/64058" | ||
96 | }, | ||
97 | { | ||
98 | "archive": 1, | ||
99 | "date_added": "2016-09-08T11:55:58+0200", | ||
100 | "favorite": 0, | ||
101 | "article__title": "[ROM][6.0.1][Layers][N5] TipsyOS official builds {UBER TCs}", | ||
102 | "article__url": "http://forum.xda-developers.com/google-nexus-5/development/rom-tipsyos-official-builds-uber-tcs-t3325989" | ||
103 | }, | ||
104 | { | ||
105 | "archive": 0, | ||
106 | "date_added": "2016-09-08T11:55:58+0200", | ||
107 | "favorite": 0, | ||
108 | "article__title": "Top 15 Podcasts All Web Developers Should Follow - Envato Tuts+ Code Article", | ||
109 | "article__url": "http://code.tutsplus.com/articles/top-15-podcasts-all-web-developers-should-follow--net-14461" | ||
110 | }, | ||
111 | { | ||
112 | "archive": 1, | ||
113 | "date_added": "2016-09-08T11:55:58+0200", | ||
114 | "favorite": 0, | ||
115 | "article__title": "University of Mississippi", | ||
116 | "article__url": "http://olemiss.edu" | ||
117 | }, | ||
118 | { | ||
119 | "archive": 1, | ||
120 | "date_added": "2016-09-08T11:55:58+0200", | ||
121 | "favorite": 0, | ||
122 | "article__title": "FinnChristiansen.de Jetzt Dank Let’s Encrypt Per HTTPS Erreichbar", | ||
123 | "article__url": "https://www.finnchristiansen.de/2015/12/06/finnchristiansen-de-jetzt-dank-lets-encrypt-per-https-erreichbar/" | ||
124 | }, | ||
125 | { | ||
126 | "archive": 1, | ||
127 | "date_added": "2016-09-08T11:55:58+0200", | ||
128 | "favorite": 0, | ||
129 | "article__title": "Le développeur et l'ingénierie logicielle", | ||
130 | "article__url": "http://wemucs.com/le-developpeur-et-lingenierie-logicielle/" | ||
131 | }, | ||
132 | { | ||
133 | "archive": 1, | ||
134 | "date_added": "2016-09-08T11:55:58+0200", | ||
135 | "favorite": 0, | ||
136 | "article__title": "The Role of Methylation in Gene Expression", | ||
137 | "article__url": "http://www.nature.com/scitable/topicpage/the-role-of-methylation-in-gene-expression-1070" | ||
138 | }, | ||
139 | { | ||
140 | "archive": 1, | ||
141 | "date_added": "2016-09-08T11:55:58+0200", | ||
142 | "favorite": 0, | ||
143 | "article__title": "E-Mail-Adresse kostenlos, FreeMail, De-Mail & Nachrichten", | ||
144 | "article__url": "http://web.de" | ||
145 | }, | ||
146 | { | ||
147 | "archive": 1, | ||
148 | "date_added": "2016-09-08T11:55:58+0200", | ||
149 | "favorite": 0, | ||
150 | "article__title": "OpenSSH Server on Arch Linux | DominicM test", | ||
151 | "article__url": "http://dominicm.com/openssh-server-arch-linux/" | ||
152 | }, | ||
153 | { | ||
154 | "archive": 1, | ||
155 | "date_added": "2016-09-08T11:55:58+0200", | ||
156 | "favorite": 0, | ||
157 | "article__title": "Site Moved | Site Help", | ||
158 | "article__url": "http://g1.com/help/sitemoved.asp" | ||
159 | }, | ||
160 | { | ||
161 | "archive": 1, | ||
162 | "date_added": "2016-09-08T11:55:58+0200", | ||
163 | "favorite": 0, | ||
164 | "article__title": "#Maroc : le stylo anti-pédophiles EAGLE d’AMESYS est moins bien configuré que les faux-lowers Twitter du roi Mohammed VI", | ||
165 | "article__url": "https://reflets.info/maroc-le-stylo-anti-pedophiles-eagle-damesys-est-moins-bien-configure-que-les-faux-lowers-twitter-du-roi-mohammed-vi/" | ||
166 | }, | ||
167 | { | ||
168 | "archive": 1, | ||
169 | "date_added": "2016-09-08T11:55:58+0200", | ||
170 | "favorite": 0, | ||
171 | "article__title": "Simple Cloud Infrastructure for Developers", | ||
172 | "article__url": "https://www.digitalocean.com/" | ||
173 | } | ||
174 | ], | ||
175 | "recommendations": [] | ||
176 | } | ||
diff --git a/tests/Wallabag/ImportBundle/fixtures/wallabag-v2-read.json b/tests/Wallabag/ImportBundle/fixtures/wallabag-v2-read.json index 3fa0bddf..d8609280 100644 --- a/tests/Wallabag/ImportBundle/fixtures/wallabag-v2-read.json +++ b/tests/Wallabag/ImportBundle/fixtures/wallabag-v2-read.json | |||
@@ -4,6 +4,8 @@ | |||
4 | "title": "Wikimedia Foundation removes The Diary of Anne Frank due to copyright law requirements « Wikimedia blog", | 4 | "title": "Wikimedia Foundation removes The Diary of Anne Frank due to copyright law requirements « Wikimedia blog", |
5 | "url": "https://blog.wikimedia.org/2016/02/10/anne-frank-diary-removal/", | 5 | "url": "https://blog.wikimedia.org/2016/02/10/anne-frank-diary-removal/", |
6 | "is_archived": true, | 6 | "is_archived": true, |
7 | "created_at": "2016-09-08T11:55:58+0200", | ||
8 | "updated_at": "2016-09-08T11:57:16+0200", | ||
7 | "is_starred": false, | 9 | "is_starred": false, |
8 | "content": "<p><a href=\"https://commons.wikimedia.org/wiki/File:AnneFrankSchoolPhoto.jpg\" rel=\"attachment wp-att-45105\"><img class=\"alignnone size-full wp-image-45105\" src=\"https://wikimediablog.files.wordpress.com/2016/02/annefrankschoolphoto.jpg?w=316&h=520\" alt=\"AnneFrankSchoolPhoto\" width=\"316\" height=\"520\"/></a><br/><small><i>Anne Frank in 1940. <a href=\"https://commons.wikimedia.org/wiki/File:AnneFrankSchoolPhoto.jpg\">Photo</a> by Collectie Anne Frank Stichting Amsterdam, public domain.</i></small></p>\n<p>Today, in an unfortunate example of the overreach of the United States’ current copyright law, the Wikimedia Foundation removed the Dutch-language text of <a href=\"https://en.wikipedia.org/wiki/The_Diary_of_a_Young_Girl\"><em>The Diary of a Young Girl</em></a>—more commonly known in English as the <em>Diary of Anne Frank—</em>from Wikisource.<sup id=\"one\"><a href=\"https://blog.wikimedia.org/2016/02/10/anne-frank-diary-removal/#cite1\">[1]</a></sup></p>\n<p>We took this action to comply with the United States’ <a href=\"https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act\">Digital Millennium Copyright Act</a> (DMCA), as we believe the diary is still under US copyright protection under the law as it is currently written. Nevertheless, our removal serves as an excellent example of why the law should be changed to prevent repeated extensions of copyright terms, an issue that has plagued our communities <a href=\"https://en.wikipedia.org/wiki/Wikipedia:Wikipedia_Signpost/2014-02-26/News_and_notes\">for years</a>.</p>\n<h3>What prompted us to remove the diary?</h3>\n<p>The deletion was required because the Foundation is under the jurisdiction of US law and is therefore subject to the DMCA, specifically <a href=\"https://www.law.cornell.edu/uscode/text/17/512\" target=\"_blank\">title 17, chapter 5, section 512 of the United States Code</a>. As we <a href=\"https://meta.wikimedia.org/wiki/Legal/URAA_Statement\" target=\"_blank\">noted</a> in 2013, “The location of the servers, incorporation, and headquarters are just three of many factors that establish US jurisdiction … if infringing content is linked to or embedded in Wikimedia projects, then the Foundation may still be subject to liability for such use—either as a direct or contributory infringer.</p>\n<p>Based on email discussions sent to the Wikimedia Foundation at legal[at]wikimedia.org, we determined that the Wikimedia Foundation had either “actual knowledge” (i in the statute quoted below) or what is commonly called “red flag knowledge” (ii in the statute quoted below) that the Anne Frank text was hosted on Wikisource and was under copyright. The statute section states that a service provider is only protected by the DMCA when it:</p>\n<p><strong>(i) </strong>does not have actual knowledge that the material or an activity using the material on the system or network is infringing;</p>\n<p><strong>(ii) </strong>in the absence of such actual knowledge, is not aware of facts or circumstances from which infringing activity is apparent; or</p>\n<p>(The rest applies when we get a proper DMCA takedown notice.)</p>\n<p>Of particular concern, the US’ <a href=\"https://en.wikipedia.org/wiki/United_States_Court_of_Appeals_for_the_Ninth_Circuit\">9th Circuit Court of Appeals</a> stated in their ruling for <a href=\"http://law.justia.com/cases/federal/appellate-courts/ca9/09-55902/09-55902-2013-03-14.html\"><em>UMG Recordings, Inc. v. Shelter Capital Partners LLC</em></a> that in circumstances where a hosting provider (like the Wikimedia Foundation) is informed by a third party (like an unrelated user) about infringing copyrighted content, that would likely constitute either actual or red flag knowledge under the DMCA.</p>\n<p>We believe, based on the detail and specificity contained in the emails, that we received that we had actual knowledge sufficient for the DMCA to require us to perform a takedown even in the absence of a demand letter.</p>\n<h3>How is the diary still copyrighted?</h3>\n<p>You may wonder why or how the Anne Frank text is copyrighted at all, as <a href=\"https://en.wikipedia.org/wiki/Anne_Frank\">Anne Frank</a> died in February 1945. With 70 years having passed since her death, the text may have passed into public domain in the Netherlands on January 1, 2016, where it was first published, although <a href=\"http://www.npr.org/sections/thetwo-way/2015/12/31/461606275/mein-kampf-enters-public-domain-arguably-anne-franks-diary-may-too\">there is still some dispute about this</a>.</p>\n<p>However, in the United States, the Anne Frank original text will be under copyright until 2042. This is the result of several factors coming together, and the English-language Wikipedia has actually covered this issue with a multi-part test on its <a href=\"https://en.wikipedia.org/wiki/Wikipedia:Non-U.S._copyrights\">non-US copyrights content guideline.</a></p>\n<p>In short, there are three major laws that together make the diary still copyrighted:</p>\n<ol><li>In general, the U.S. copyright for works published before 1978 is 95 years from date of publication. This came about because copyrights in the U.S. were originally for 28 years, with the ability to then extend that for a second 28 years (making a total of 56). Starting with the <a href=\"https://en.wikipedia.org/wiki/Copyright_Act_of_1976\">1976 Copyright Act</a> and extending to several more acts, the renewal became automatic and was extended. Today, the total term of works published before 1978 is 95 years from date of publication.</li>\n<li>Foreign works of countries that are treaty partners to the United States are covered as if they were US works.</li>\n<li>Even if a country was not a treaty partner under copyright law at the time of a publication, the <a href=\"https://en.wikipedia.org/wiki/Uruguay_Round_Agreements_Act\">1994 Uruguay Round Agreements Act</a> (URAA) restored copyright to works that:\n<ul><li>had been published in a foreign country</li>\n<li>were still under copyright in that country in 1996</li>\n<li>and would have had U.S. copyright but for the fact they were published abroad.</li>\n</ul></li>\n</ol>\n<p>Court challenges to the URAA have all failed, with the most notable (<a href=\"https://en.wikipedia.org/wiki/Golan_v._Holder\"><em>Golan v. Holder</em></a>) resulting in a Supreme Court ruling that upheld the URAA.</p>\n<p>What that means for Anne Frank’s diary is unfortunately simple: no matter how it wound up in the United States and regardless of what formal copyright notices they used, the US grants it copyright until the year 2042, or 95 years after its original publication in 1947.</p>\n<p>Under current copyright law, this remains true regardless of its copyright status anywhere else in the world and regardless of whether it may have been in the public domain in the United States in the past.</p>\n<p><a href=\"https://wikimediafoundation.org/wiki/User:Jrogers_(WMF)\"><em>Jacob Rogers</em></a><em>, Legal Counsel*<br/>Wikimedia Foundation</em></p>\n<p><em>*Special thanks to </em><a href=\"https://wikimediafoundation.org/wiki/User:AMangalick_(WMF)\"><em>Anisha Mangalick</em></a><em>, Legal Fellow, for her assistance in this matter.</em></p>\n<p><a href=\"https://blog.wikimedia.org/2016/02/10/anne-frank-diary-removal/#one\">[1]</a> The diary text was originally located at <a href=\"https://nl.wikisource.org/wiki/Het_Achterhuis_(Anne_Frank)\" rel=\"nofollow\">https://nl.wikisource.org/wiki/Het_Achterhuis_(Anne_Frank)</a>.</p>\n<p><em>This article was edited to clarify that it is not just the location of the Wikimedia Foundation’s servers that determine whether we fall in US jurisdiction.</em></p>\n\t\t\t\t\t\t\t\t\t\t\t", | 10 | "content": "<p><a href=\"https://commons.wikimedia.org/wiki/File:AnneFrankSchoolPhoto.jpg\" rel=\"attachment wp-att-45105\"><img class=\"alignnone size-full wp-image-45105\" src=\"https://wikimediablog.files.wordpress.com/2016/02/annefrankschoolphoto.jpg?w=316&h=520\" alt=\"AnneFrankSchoolPhoto\" width=\"316\" height=\"520\"/></a><br/><small><i>Anne Frank in 1940. <a href=\"https://commons.wikimedia.org/wiki/File:AnneFrankSchoolPhoto.jpg\">Photo</a> by Collectie Anne Frank Stichting Amsterdam, public domain.</i></small></p>\n<p>Today, in an unfortunate example of the overreach of the United States’ current copyright law, the Wikimedia Foundation removed the Dutch-language text of <a href=\"https://en.wikipedia.org/wiki/The_Diary_of_a_Young_Girl\"><em>The Diary of a Young Girl</em></a>—more commonly known in English as the <em>Diary of Anne Frank—</em>from Wikisource.<sup id=\"one\"><a href=\"https://blog.wikimedia.org/2016/02/10/anne-frank-diary-removal/#cite1\">[1]</a></sup></p>\n<p>We took this action to comply with the United States’ <a href=\"https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act\">Digital Millennium Copyright Act</a> (DMCA), as we believe the diary is still under US copyright protection under the law as it is currently written. Nevertheless, our removal serves as an excellent example of why the law should be changed to prevent repeated extensions of copyright terms, an issue that has plagued our communities <a href=\"https://en.wikipedia.org/wiki/Wikipedia:Wikipedia_Signpost/2014-02-26/News_and_notes\">for years</a>.</p>\n<h3>What prompted us to remove the diary?</h3>\n<p>The deletion was required because the Foundation is under the jurisdiction of US law and is therefore subject to the DMCA, specifically <a href=\"https://www.law.cornell.edu/uscode/text/17/512\" target=\"_blank\">title 17, chapter 5, section 512 of the United States Code</a>. As we <a href=\"https://meta.wikimedia.org/wiki/Legal/URAA_Statement\" target=\"_blank\">noted</a> in 2013, “The location of the servers, incorporation, and headquarters are just three of many factors that establish US jurisdiction … if infringing content is linked to or embedded in Wikimedia projects, then the Foundation may still be subject to liability for such use—either as a direct or contributory infringer.</p>\n<p>Based on email discussions sent to the Wikimedia Foundation at legal[at]wikimedia.org, we determined that the Wikimedia Foundation had either “actual knowledge” (i in the statute quoted below) or what is commonly called “red flag knowledge” (ii in the statute quoted below) that the Anne Frank text was hosted on Wikisource and was under copyright. The statute section states that a service provider is only protected by the DMCA when it:</p>\n<p><strong>(i) </strong>does not have actual knowledge that the material or an activity using the material on the system or network is infringing;</p>\n<p><strong>(ii) </strong>in the absence of such actual knowledge, is not aware of facts or circumstances from which infringing activity is apparent; or</p>\n<p>(The rest applies when we get a proper DMCA takedown notice.)</p>\n<p>Of particular concern, the US’ <a href=\"https://en.wikipedia.org/wiki/United_States_Court_of_Appeals_for_the_Ninth_Circuit\">9th Circuit Court of Appeals</a> stated in their ruling for <a href=\"http://law.justia.com/cases/federal/appellate-courts/ca9/09-55902/09-55902-2013-03-14.html\"><em>UMG Recordings, Inc. v. Shelter Capital Partners LLC</em></a> that in circumstances where a hosting provider (like the Wikimedia Foundation) is informed by a third party (like an unrelated user) about infringing copyrighted content, that would likely constitute either actual or red flag knowledge under the DMCA.</p>\n<p>We believe, based on the detail and specificity contained in the emails, that we received that we had actual knowledge sufficient for the DMCA to require us to perform a takedown even in the absence of a demand letter.</p>\n<h3>How is the diary still copyrighted?</h3>\n<p>You may wonder why or how the Anne Frank text is copyrighted at all, as <a href=\"https://en.wikipedia.org/wiki/Anne_Frank\">Anne Frank</a> died in February 1945. With 70 years having passed since her death, the text may have passed into public domain in the Netherlands on January 1, 2016, where it was first published, although <a href=\"http://www.npr.org/sections/thetwo-way/2015/12/31/461606275/mein-kampf-enters-public-domain-arguably-anne-franks-diary-may-too\">there is still some dispute about this</a>.</p>\n<p>However, in the United States, the Anne Frank original text will be under copyright until 2042. This is the result of several factors coming together, and the English-language Wikipedia has actually covered this issue with a multi-part test on its <a href=\"https://en.wikipedia.org/wiki/Wikipedia:Non-U.S._copyrights\">non-US copyrights content guideline.</a></p>\n<p>In short, there are three major laws that together make the diary still copyrighted:</p>\n<ol><li>In general, the U.S. copyright for works published before 1978 is 95 years from date of publication. This came about because copyrights in the U.S. were originally for 28 years, with the ability to then extend that for a second 28 years (making a total of 56). Starting with the <a href=\"https://en.wikipedia.org/wiki/Copyright_Act_of_1976\">1976 Copyright Act</a> and extending to several more acts, the renewal became automatic and was extended. Today, the total term of works published before 1978 is 95 years from date of publication.</li>\n<li>Foreign works of countries that are treaty partners to the United States are covered as if they were US works.</li>\n<li>Even if a country was not a treaty partner under copyright law at the time of a publication, the <a href=\"https://en.wikipedia.org/wiki/Uruguay_Round_Agreements_Act\">1994 Uruguay Round Agreements Act</a> (URAA) restored copyright to works that:\n<ul><li>had been published in a foreign country</li>\n<li>were still under copyright in that country in 1996</li>\n<li>and would have had U.S. copyright but for the fact they were published abroad.</li>\n</ul></li>\n</ol>\n<p>Court challenges to the URAA have all failed, with the most notable (<a href=\"https://en.wikipedia.org/wiki/Golan_v._Holder\"><em>Golan v. Holder</em></a>) resulting in a Supreme Court ruling that upheld the URAA.</p>\n<p>What that means for Anne Frank’s diary is unfortunately simple: no matter how it wound up in the United States and regardless of what formal copyright notices they used, the US grants it copyright until the year 2042, or 95 years after its original publication in 1947.</p>\n<p>Under current copyright law, this remains true regardless of its copyright status anywhere else in the world and regardless of whether it may have been in the public domain in the United States in the past.</p>\n<p><a href=\"https://wikimediafoundation.org/wiki/User:Jrogers_(WMF)\"><em>Jacob Rogers</em></a><em>, Legal Counsel*<br/>Wikimedia Foundation</em></p>\n<p><em>*Special thanks to </em><a href=\"https://wikimediafoundation.org/wiki/User:AMangalick_(WMF)\"><em>Anisha Mangalick</em></a><em>, Legal Fellow, for her assistance in this matter.</em></p>\n<p><a href=\"https://blog.wikimedia.org/2016/02/10/anne-frank-diary-removal/#one\">[1]</a> The diary text was originally located at <a href=\"https://nl.wikisource.org/wiki/Het_Achterhuis_(Anne_Frank)\" rel=\"nofollow\">https://nl.wikisource.org/wiki/Het_Achterhuis_(Anne_Frank)</a>.</p>\n<p><em>This article was edited to clarify that it is not just the location of the Wikimedia Foundation’s servers that determine whether we fall in US jurisdiction.</em></p>\n\t\t\t\t\t\t\t\t\t\t\t", |
9 | "mimetype": "text/html", | 11 | "mimetype": "text/html", |
@@ -17,6 +19,8 @@ | |||
17 | "title": "Tails - Tails 2.0.1 is out", | 19 | "title": "Tails - Tails 2.0.1 is out", |
18 | "url": "https://tails.boum.org/news/version_2.0.1/index.en.html", | 20 | "url": "https://tails.boum.org/news/version_2.0.1/index.en.html", |
19 | "is_archived": false, | 21 | "is_archived": false, |
22 | "created_at": "2016-09-08T11:55:58+0200", | ||
23 | "updated_at": "2016-09-08T11:57:16+0200", | ||
20 | "is_starred": false, | 24 | "is_starred": false, |
21 | "content": "<div id=\"pagebody\" readability=\"39\">\n<p>This release fixes <a href=\"https://tails.boum.org/security/Numerous_security_holes_in_2.0/index.en.html\">numerous security issues</a>. All users must upgrade as soon as possible.</p>\n<div class=\"toc\">\n<ol><li class=\"L1\"><a href=\"https://tails.boum.org/news/version_2.0.1/index.en.html#index1h1\">Changes</a></li>\n<li class=\"L1\"><a href=\"https://tails.boum.org/news/version_2.0.1/index.en.html#index2h1\">Known issues</a></li>\n<li class=\"L1\"><a href=\"https://tails.boum.org/news/version_2.0.1/index.en.html#index3h1\">Download or upgrade</a></li>\n<li class=\"L1\"><a href=\"https://tails.boum.org/news/version_2.0.1/index.en.html#index4h1\">What's coming up?</a></li>\n</ol></div>\n<h2>New features</h2>\n<ul><li readability=\"11\">\n<p>Tails now uses the GNOME Shell desktop environment, in its Classic mode. GNOME Shell provides a modern, simple, and actively developed desktop environment. The Classic mode keeps the traditional Applications, Places menu, and windows list. Accessibility and non-Latin input sources are also better integrated.</p>\n<p>To find your way around, <a href=\"https://tails.boum.org/doc/first_steps/introduction_to_gnome_and_the_tails_desktop/index.en.html\">read our introduction to GNOME and the Tails desktop.</a></p>\n<table class=\"img\"><caption>The desktop and Applications menu</caption>\n<tr><td><img alt=\"Tails 2.0 desktop with applications menu unfolded\" class=\"img\" height=\"384\" src=\"https://tails.boum.org/inc/release_notes/2.0/applications_menu.png\" width=\"512\"/></td>\n</tr></table><table class=\"img\"><caption>The activities overview</caption>\n<tr><td><img alt=\"Tails 2.0 activities overview\" class=\"img\" height=\"384\" src=\"https://tails.boum.org/inc/release_notes/2.0/activities_overview.png\" width=\"512\"/></td>\n</tr></table></li>\n</ul><h2>Upgrades and changes</h2>\n<ul><li readability=\"2\">\n<p>Debian 8 upgrades most included software, for example:</p>\n<ul><li>Many core GNOME utilities from 3.4 to 3.14: Files, Disks, Videos, etc.</li>\n<li>LibreOffice from 3.5 to 4.3</li>\n<li>PiTiVi from 0.15 to 0.93</li>\n<li>Git from 1.7.10 to 2.1.4</li>\n<li>Poedit from 1.5.4 to 1.6.10</li>\n<li>Liferea from 1.8.6 to 1.10</li>\n</ul></li>\n<li readability=\"1\">\n<p>Update Tor Browser to 5.5 (based on Firefox 38.6.0 ESR):</p>\n<ul><li>Add Japanese support.</li>\n</ul></li>\n<li readability=\"2\">\n<p>Remove the Windows camouflage which is currently broken in GNOME Shell. We started working on <a href=\"https://labs.riseup.net/code/issues/10830\">adding it back</a> but <a href=\"https://tails.boum.org/news/windows_camouflage_jessie/index.en.html\">your help is needed</a>!</p>\n</li>\n<li readability=\"1\">\n<p>Change to <code>systemd</code> as init system and use it to:</p>\n<ul><li>Sandbox many services using Linux namespaces and make them harder to exploit.</li>\n<li>Make the launching of Tor and the memory wipe on shutdown more robust.</li>\n<li>Sanitize our code base by replacing many custom scripts.</li>\n</ul></li>\n<li readability=\"1\">\n<p>Update most firmware packages which might improve hardware compatibility.</p>\n</li>\n<li readability=\"1\">\n<p>Notify the user if Tails is running from a non-free virtualization software.</p>\n</li>\n<li readability=\"3\">\n<p>Remove Claws Mail, replaced by <a href=\"https://tails.boum.org/doc/anonymous_internet/icedove/index.en.html\">Icedove</a>, a rebranded version of Mozilla Thunderbird.</p>\n</li>\n</ul><h2>Fixed problems</h2>\n<ul><li readability=\"1\">\n<p>HiDPI displays are better supported. (<a href=\"https://labs.riseup.net/code/issues/8659\">#8659</a>)</p>\n</li>\n<li readability=\"3\">\n<p>Remove the option to open a download with an external application in Tor Browser as this is usually impossible due to the AppArmor confinement. (<a href=\"https://labs.riseup.net/code/issues/9285\">#9285</a>)</p>\n</li>\n<li readability=\"1\">\n<p>Close Vidalia before restarting Tor.</p>\n</li>\n<li readability=\"2\">\n<p>Allow Videos to access the DVD drive. (<a href=\"https://labs.riseup.net/code/issues/10455\">#10455</a>, <a href=\"https://labs.riseup.net/code/issues/9990\">#9990</a>)</p>\n</li>\n<li readability=\"1\">\n<p>Allow configuring printers without administration password. (<a href=\"https://labs.riseup.net/code/issues/8443\">#8443</a>)</p>\n</li>\n</ul>\n<p>See the current list of <a href=\"https://tails.boum.org/support/known_issues/index.en.html\">known issues</a>.</p>\n<p>Go to the <a href=\"https://tails.boum.org/download/index.en.html\">download</a> or <a href=\"https://tails.boum.org/doc/first_steps/upgrade/index.en.html\">upgrade</a> page.</p>\n<p>If your Tails does not boot after an automatic upgrade, please <a href=\"https://tails.boum.org/doc/first_steps/upgrade/index.en.html#manual\">upgrade your Tails manually</a>.</p>\n<p>The next Tails release is <a href=\"https://tails.boum.org/contribute/calendar/\">scheduled</a> for March 08.</p>\n<p>Have a look at our <a href=\"https://labs.riseup.net/code/projects/tails/roadmap\">roadmap</a> to see where we are heading to.</p>\n<p>We need your help and there are many ways to <a href=\"https://tails.boum.org/contribute/index.en.html\">contribute to Tails</a> (<a href=\"https://tails.boum.org/contribute/how/donate/index.en.html\">donating</a> is only one of them). Come <a href=\"https://tails.boum.org/contribute/talk/\">talk to us</a>!</p>\n</div><div id=\"footer\" class=\"pagefooter\" role=\"contentinfo\" readability=\"15\">\n<p>Tags: <a href=\"https://tails.boum.org/tags/announce/\" rel=\"tag\">announce</a></p>\n<p>Pages linking to this one: <a href=\"https://tails.boum.org/inc/stable_i386_release_notes/index.en.html\">inc/stable i386 release notes</a> <a href=\"https://tails.boum.org/security/Numerous_security_holes_in_2.0/index.en.html\">security/Numerous security holes in 2.0</a></p>\n<p>Last edited Sat 13 Feb 2016 02:23:58 PM CET </p>\n</div>", | 25 | "content": "<div id=\"pagebody\" readability=\"39\">\n<p>This release fixes <a href=\"https://tails.boum.org/security/Numerous_security_holes_in_2.0/index.en.html\">numerous security issues</a>. All users must upgrade as soon as possible.</p>\n<div class=\"toc\">\n<ol><li class=\"L1\"><a href=\"https://tails.boum.org/news/version_2.0.1/index.en.html#index1h1\">Changes</a></li>\n<li class=\"L1\"><a href=\"https://tails.boum.org/news/version_2.0.1/index.en.html#index2h1\">Known issues</a></li>\n<li class=\"L1\"><a href=\"https://tails.boum.org/news/version_2.0.1/index.en.html#index3h1\">Download or upgrade</a></li>\n<li class=\"L1\"><a href=\"https://tails.boum.org/news/version_2.0.1/index.en.html#index4h1\">What's coming up?</a></li>\n</ol></div>\n<h2>New features</h2>\n<ul><li readability=\"11\">\n<p>Tails now uses the GNOME Shell desktop environment, in its Classic mode. GNOME Shell provides a modern, simple, and actively developed desktop environment. The Classic mode keeps the traditional Applications, Places menu, and windows list. Accessibility and non-Latin input sources are also better integrated.</p>\n<p>To find your way around, <a href=\"https://tails.boum.org/doc/first_steps/introduction_to_gnome_and_the_tails_desktop/index.en.html\">read our introduction to GNOME and the Tails desktop.</a></p>\n<table class=\"img\"><caption>The desktop and Applications menu</caption>\n<tr><td><img alt=\"Tails 2.0 desktop with applications menu unfolded\" class=\"img\" height=\"384\" src=\"https://tails.boum.org/inc/release_notes/2.0/applications_menu.png\" width=\"512\"/></td>\n</tr></table><table class=\"img\"><caption>The activities overview</caption>\n<tr><td><img alt=\"Tails 2.0 activities overview\" class=\"img\" height=\"384\" src=\"https://tails.boum.org/inc/release_notes/2.0/activities_overview.png\" width=\"512\"/></td>\n</tr></table></li>\n</ul><h2>Upgrades and changes</h2>\n<ul><li readability=\"2\">\n<p>Debian 8 upgrades most included software, for example:</p>\n<ul><li>Many core GNOME utilities from 3.4 to 3.14: Files, Disks, Videos, etc.</li>\n<li>LibreOffice from 3.5 to 4.3</li>\n<li>PiTiVi from 0.15 to 0.93</li>\n<li>Git from 1.7.10 to 2.1.4</li>\n<li>Poedit from 1.5.4 to 1.6.10</li>\n<li>Liferea from 1.8.6 to 1.10</li>\n</ul></li>\n<li readability=\"1\">\n<p>Update Tor Browser to 5.5 (based on Firefox 38.6.0 ESR):</p>\n<ul><li>Add Japanese support.</li>\n</ul></li>\n<li readability=\"2\">\n<p>Remove the Windows camouflage which is currently broken in GNOME Shell. We started working on <a href=\"https://labs.riseup.net/code/issues/10830\">adding it back</a> but <a href=\"https://tails.boum.org/news/windows_camouflage_jessie/index.en.html\">your help is needed</a>!</p>\n</li>\n<li readability=\"1\">\n<p>Change to <code>systemd</code> as init system and use it to:</p>\n<ul><li>Sandbox many services using Linux namespaces and make them harder to exploit.</li>\n<li>Make the launching of Tor and the memory wipe on shutdown more robust.</li>\n<li>Sanitize our code base by replacing many custom scripts.</li>\n</ul></li>\n<li readability=\"1\">\n<p>Update most firmware packages which might improve hardware compatibility.</p>\n</li>\n<li readability=\"1\">\n<p>Notify the user if Tails is running from a non-free virtualization software.</p>\n</li>\n<li readability=\"3\">\n<p>Remove Claws Mail, replaced by <a href=\"https://tails.boum.org/doc/anonymous_internet/icedove/index.en.html\">Icedove</a>, a rebranded version of Mozilla Thunderbird.</p>\n</li>\n</ul><h2>Fixed problems</h2>\n<ul><li readability=\"1\">\n<p>HiDPI displays are better supported. (<a href=\"https://labs.riseup.net/code/issues/8659\">#8659</a>)</p>\n</li>\n<li readability=\"3\">\n<p>Remove the option to open a download with an external application in Tor Browser as this is usually impossible due to the AppArmor confinement. (<a href=\"https://labs.riseup.net/code/issues/9285\">#9285</a>)</p>\n</li>\n<li readability=\"1\">\n<p>Close Vidalia before restarting Tor.</p>\n</li>\n<li readability=\"2\">\n<p>Allow Videos to access the DVD drive. (<a href=\"https://labs.riseup.net/code/issues/10455\">#10455</a>, <a href=\"https://labs.riseup.net/code/issues/9990\">#9990</a>)</p>\n</li>\n<li readability=\"1\">\n<p>Allow configuring printers without administration password. (<a href=\"https://labs.riseup.net/code/issues/8443\">#8443</a>)</p>\n</li>\n</ul>\n<p>See the current list of <a href=\"https://tails.boum.org/support/known_issues/index.en.html\">known issues</a>.</p>\n<p>Go to the <a href=\"https://tails.boum.org/download/index.en.html\">download</a> or <a href=\"https://tails.boum.org/doc/first_steps/upgrade/index.en.html\">upgrade</a> page.</p>\n<p>If your Tails does not boot after an automatic upgrade, please <a href=\"https://tails.boum.org/doc/first_steps/upgrade/index.en.html#manual\">upgrade your Tails manually</a>.</p>\n<p>The next Tails release is <a href=\"https://tails.boum.org/contribute/calendar/\">scheduled</a> for March 08.</p>\n<p>Have a look at our <a href=\"https://labs.riseup.net/code/projects/tails/roadmap\">roadmap</a> to see where we are heading to.</p>\n<p>We need your help and there are many ways to <a href=\"https://tails.boum.org/contribute/index.en.html\">contribute to Tails</a> (<a href=\"https://tails.boum.org/contribute/how/donate/index.en.html\">donating</a> is only one of them). Come <a href=\"https://tails.boum.org/contribute/talk/\">talk to us</a>!</p>\n</div><div id=\"footer\" class=\"pagefooter\" role=\"contentinfo\" readability=\"15\">\n<p>Tags: <a href=\"https://tails.boum.org/tags/announce/\" rel=\"tag\">announce</a></p>\n<p>Pages linking to this one: <a href=\"https://tails.boum.org/inc/stable_i386_release_notes/index.en.html\">inc/stable i386 release notes</a> <a href=\"https://tails.boum.org/security/Numerous_security_holes_in_2.0/index.en.html\">security/Numerous security holes in 2.0</a></p>\n<p>Last edited Sat 13 Feb 2016 02:23:58 PM CET </p>\n</div>", |
22 | "mimetype": "text/html", | 26 | "mimetype": "text/html", |
diff --git a/tests/Wallabag/ImportBundle/fixtures/wallabag-v2.json b/tests/Wallabag/ImportBundle/fixtures/wallabag-v2.json index 37c59668..efa8faf2 100644 --- a/tests/Wallabag/ImportBundle/fixtures/wallabag-v2.json +++ b/tests/Wallabag/ImportBundle/fixtures/wallabag-v2.json | |||
@@ -4,6 +4,8 @@ | |||
4 | "title": "Site d'information français d'actualités indépendant et participatif en ligne | Mediapart", | 4 | "title": "Site d'information français d'actualités indépendant et participatif en ligne | Mediapart", |
5 | "url": "https://www.mediapart.fr/", | 5 | "url": "https://www.mediapart.fr/", |
6 | "is_archived": false, | 6 | "is_archived": false, |
7 | "created_at": "2016-09-08T11:55:58+0200", | ||
8 | "updated_at": "2016-09-08T11:57:16+0200", | ||
7 | "is_starred": false, | 9 | "is_starred": false, |
8 | "content": "<div alt=\"li\">Édition <a href=\"https://blogs.mediapart.fr/edition/camedia-0\">CAMédia</a>\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/edition/camedia/article/180116/deux-nouvelles-editions-pour-debattre-dans-le-club-sur-la-laicite-et-sur-la-democratie\">Deux nouvelles éditions pour débattre dans le club sur la laïcité et sur la démocratie</a></h3>\n<p>18 janv. 2016 | Par </p>\n<p>CAMédia après un échange sur « l'éthique du débat » a lancé deux discussions , l'une sur le thème de la laïcité, l'autre ( encore en cours) sur celui de la démocratie. Nous sommes heureux de pouvoir signaler la création de deux nouvelles éditions participatives sur ces thèmes. Nous vous invitons à les lire et à participer à leurs débats.</p>\n</div><div alt=\"li\">\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/lucile-longre/blog/170116/de-limportance-de-rever-eloge-du-merveilleux\">De l'importance de rêver, éloge du merveilleux</a></h3>\n<p>17 janv. 2016 | Par </p>\n<p>Je parlerai ici des rêves comme moteur de vie, de ces rêves qui vous rattachent et vous font espérer à ce qu’il y a de plus humain dans l’homme, même au milieu de la plus noire des détresses.</p>\n</div><div alt=\"li\">\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/barbara-romagnan/blog/180116/fins-dune-toute-puissance\">Fin(s) d'une toute-puissance</a></h3>\n<p>18 janv. 2016 | Par </p>\n<p>En ce début d’année, je recommande la lecture du dernier ouvrage de Guillaume Duval, La France ne sera jamais plus une grande puissance ? Tant mieux !</p>\n</div><div alt=\"li\">\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/jean-pierre-thibaudat/blog/170116/l-allier-departement-de-destruction-massive-du-tissu-culturel\">L’Allier, département de destruction massive du tissu culturel</a></h3>\n<p>18 janv. 2016 | Par </p>\n<p>Les temps sont durs pour les petites structures, les associations culturelles qui, de bourgades en villages, travaillent au cœur des régions. Leurs subventions sont souvent revues à la baisse. Le département de l’Allier les a carrément supprimées. Pour favoriser « l’événementiel ».</p>\n</div><div alt=\"li\">Édition <a href=\"https://blogs.mediapart.fr/edition/les-invites-de-mediapart\">Les invités de Mediapart</a>\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/edition/les-invites-de-mediapart/article/180116/la-democratie-deja-attaquee-par-la-cooperation-reglementaire-transatlantiqu\">La démocratie déjà attaquée par la coopération réglementaire transatlantique</a></h3>\n<p>18 janv. 2016 | Par </p>\n<p>Lora Verheecke et David Lundy travaillent pour Corporate Europe Observatory, une ONG basée à Bruxelles qui enquête sur le pouvoir des lobbies des grandes entreprises sur la politique de l’Union européenne. Ils révèlent que depuis 25 ans le projet de « coopération réglementaire » mené par l’Union européenne et les États-Unis a été dominé par les grandes entreprises. ET que le TTIP cherche à entériner ce projet.</p>\n</div><div alt=\"li\">\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/jacqueline-derens/blog/180116/2016-une-annee-test-pour-jacob-zuma-et-son-gouvernement\">2016, une année test pour Jacob Zuma et son gouvernement</a></h3>\n<p>18 janv. 2016 | Par </p>\n<p>Les turbulences de l’an passé ont toutes les chances de continuer à troubler le climat politique et social de l’Afrique du Sud en 2016. La situation exige des changements profonds dans la conduite des affaires du pays. Jacob Zuma tout en admettant la nécessité de ces changements, est-il l’homme de la situation ? Son gouvernement répondra-t-il aux attentes des citoyens sud-africains ?</p>\n</div><div alt=\"li\">\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/marie-cosnay/blog/140116/un-mal-fou-janvier-2016\">Un mal fou (janvier 2016)</a></h3>\n<p>14 janv. 2016 | Par </p>\n<p>J’ai une fringale d’aventure, d’aventures à venir. J’ai la fringale de la fringale des aventures et soudain, rupture. Je n’y arrive plus, tout est bloqué, tout empêché. Faut dire que depuis un an environ, tout est devenu plus compliqué. Ecrire va de moins en moins de soi.</p>\n</div><div alt=\"li\">\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/jean-pierre-veran/blog/170116/redoublement-le-changement-bas-bruit\">Redoublement : le changement à bas bruit ?</a></h3>\n<p>17 janv. 2016 | Par </p>\n<p>S’il est une caractéristique de la forme scolaire française bien établie dans la culture des personnels, des élèves et des parents, c’est bien le redoublement, censé sanctionner des résultats insuffisants pour envisager le passage dans la classe supérieure. Or, en ce domaine, l’évolution est nette.</p>\n</div><div alt=\"li\">\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/michel-de-pracontal/blog/160116/samedi-sciences-196-des-chasseurs-de-mammouths-en-arctique-il-y-45-000-ans\">Samedi-sciences (196): des chasseurs de mammouths en Arctique il y a 45 000 ans</a></h3>\n<p>16 janv. 2016 | Par <a href=\"https://blogs.mediapart.fr/michel-de-pracontal\" class=\"journalist\">Michel de Pracontal</a></p>\n<p>Les restes d’un mammouth retrouvés en Arctique sibérien, datés de 45 000 ans, portent les traces de blessures infligées par des chasseurs humains. Les scientifiques pensaient jusqu’ici que notre espèce ne s’était pas aventurée dans cette région glaciale il y a plus de 30 000 ou 35 0000 ans. En réalité, des hommes ont réussi à survivre en Arctique au moins 10 000 ans plus tôt que l’on croyait.</p>\n</div><div alt=\"li\">\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/alain-zolty/blog/140116/de-la-democratie-du-citoyen-et-de-lethique\">De la démocratie, du citoyen et de l'éthique</a></h3>\n<p>14 janv. 2016 | Par </p>\n<p>Trois ouvrages sont parus au Seuil, qui font état de la nécessité d’intégrer le citoyen dans la gouvernance de la nation. Non pas à titre consultatif mais doté d’un pouvoir délibératif pour constituer une contre-force face aux clans politico-financiers qui dominent la vie publique.</p>\n</div>", | 10 | "content": "<div alt=\"li\">Édition <a href=\"https://blogs.mediapart.fr/edition/camedia-0\">CAMédia</a>\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/edition/camedia/article/180116/deux-nouvelles-editions-pour-debattre-dans-le-club-sur-la-laicite-et-sur-la-democratie\">Deux nouvelles éditions pour débattre dans le club sur la laïcité et sur la démocratie</a></h3>\n<p>18 janv. 2016 | Par </p>\n<p>CAMédia après un échange sur « l'éthique du débat » a lancé deux discussions , l'une sur le thème de la laïcité, l'autre ( encore en cours) sur celui de la démocratie. Nous sommes heureux de pouvoir signaler la création de deux nouvelles éditions participatives sur ces thèmes. Nous vous invitons à les lire et à participer à leurs débats.</p>\n</div><div alt=\"li\">\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/lucile-longre/blog/170116/de-limportance-de-rever-eloge-du-merveilleux\">De l'importance de rêver, éloge du merveilleux</a></h3>\n<p>17 janv. 2016 | Par </p>\n<p>Je parlerai ici des rêves comme moteur de vie, de ces rêves qui vous rattachent et vous font espérer à ce qu’il y a de plus humain dans l’homme, même au milieu de la plus noire des détresses.</p>\n</div><div alt=\"li\">\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/barbara-romagnan/blog/180116/fins-dune-toute-puissance\">Fin(s) d'une toute-puissance</a></h3>\n<p>18 janv. 2016 | Par </p>\n<p>En ce début d’année, je recommande la lecture du dernier ouvrage de Guillaume Duval, La France ne sera jamais plus une grande puissance ? Tant mieux !</p>\n</div><div alt=\"li\">\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/jean-pierre-thibaudat/blog/170116/l-allier-departement-de-destruction-massive-du-tissu-culturel\">L’Allier, département de destruction massive du tissu culturel</a></h3>\n<p>18 janv. 2016 | Par </p>\n<p>Les temps sont durs pour les petites structures, les associations culturelles qui, de bourgades en villages, travaillent au cœur des régions. Leurs subventions sont souvent revues à la baisse. Le département de l’Allier les a carrément supprimées. Pour favoriser « l’événementiel ».</p>\n</div><div alt=\"li\">Édition <a href=\"https://blogs.mediapart.fr/edition/les-invites-de-mediapart\">Les invités de Mediapart</a>\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/edition/les-invites-de-mediapart/article/180116/la-democratie-deja-attaquee-par-la-cooperation-reglementaire-transatlantiqu\">La démocratie déjà attaquée par la coopération réglementaire transatlantique</a></h3>\n<p>18 janv. 2016 | Par </p>\n<p>Lora Verheecke et David Lundy travaillent pour Corporate Europe Observatory, une ONG basée à Bruxelles qui enquête sur le pouvoir des lobbies des grandes entreprises sur la politique de l’Union européenne. Ils révèlent que depuis 25 ans le projet de « coopération réglementaire » mené par l’Union européenne et les États-Unis a été dominé par les grandes entreprises. ET que le TTIP cherche à entériner ce projet.</p>\n</div><div alt=\"li\">\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/jacqueline-derens/blog/180116/2016-une-annee-test-pour-jacob-zuma-et-son-gouvernement\">2016, une année test pour Jacob Zuma et son gouvernement</a></h3>\n<p>18 janv. 2016 | Par </p>\n<p>Les turbulences de l’an passé ont toutes les chances de continuer à troubler le climat politique et social de l’Afrique du Sud en 2016. La situation exige des changements profonds dans la conduite des affaires du pays. Jacob Zuma tout en admettant la nécessité de ces changements, est-il l’homme de la situation ? Son gouvernement répondra-t-il aux attentes des citoyens sud-africains ?</p>\n</div><div alt=\"li\">\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/marie-cosnay/blog/140116/un-mal-fou-janvier-2016\">Un mal fou (janvier 2016)</a></h3>\n<p>14 janv. 2016 | Par </p>\n<p>J’ai une fringale d’aventure, d’aventures à venir. J’ai la fringale de la fringale des aventures et soudain, rupture. Je n’y arrive plus, tout est bloqué, tout empêché. Faut dire que depuis un an environ, tout est devenu plus compliqué. Ecrire va de moins en moins de soi.</p>\n</div><div alt=\"li\">\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/jean-pierre-veran/blog/170116/redoublement-le-changement-bas-bruit\">Redoublement : le changement à bas bruit ?</a></h3>\n<p>17 janv. 2016 | Par </p>\n<p>S’il est une caractéristique de la forme scolaire française bien établie dans la culture des personnels, des élèves et des parents, c’est bien le redoublement, censé sanctionner des résultats insuffisants pour envisager le passage dans la classe supérieure. Or, en ce domaine, l’évolution est nette.</p>\n</div><div alt=\"li\">\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/michel-de-pracontal/blog/160116/samedi-sciences-196-des-chasseurs-de-mammouths-en-arctique-il-y-45-000-ans\">Samedi-sciences (196): des chasseurs de mammouths en Arctique il y a 45 000 ans</a></h3>\n<p>16 janv. 2016 | Par <a href=\"https://blogs.mediapart.fr/michel-de-pracontal\" class=\"journalist\">Michel de Pracontal</a></p>\n<p>Les restes d’un mammouth retrouvés en Arctique sibérien, datés de 45 000 ans, portent les traces de blessures infligées par des chasseurs humains. Les scientifiques pensaient jusqu’ici que notre espèce ne s’était pas aventurée dans cette région glaciale il y a plus de 30 000 ou 35 0000 ans. En réalité, des hommes ont réussi à survivre en Arctique au moins 10 000 ans plus tôt que l’on croyait.</p>\n</div><div alt=\"li\">\n<h3 class=\"title\"><a href=\"https://blogs.mediapart.fr/alain-zolty/blog/140116/de-la-democratie-du-citoyen-et-de-lethique\">De la démocratie, du citoyen et de l'éthique</a></h3>\n<p>14 janv. 2016 | Par </p>\n<p>Trois ouvrages sont parus au Seuil, qui font état de la nécessité d’intégrer le citoyen dans la gouvernance de la nation. Non pas à titre consultatif mais doté d’un pouvoir délibératif pour constituer une contre-force face aux clans politico-financiers qui dominent la vie publique.</p>\n</div>", |
9 | "mimetype": "text/html", | 11 | "mimetype": "text/html", |
@@ -21,6 +23,8 @@ | |||
21 | "title": "Réfugiés: l'UE va créer 100 000 places d'accueil dans les Balkans", | 23 | "title": "Réfugiés: l'UE va créer 100 000 places d'accueil dans les Balkans", |
22 | "url": "http://www.liberation.fr/planete/2015/10/26/refugies-l-ue-va-creer-100-000-places-d-accueil-dans-les-balkans_1408867", | 24 | "url": "http://www.liberation.fr/planete/2015/10/26/refugies-l-ue-va-creer-100-000-places-d-accueil-dans-les-balkans_1408867", |
23 | "is_archived": false, | 25 | "is_archived": false, |
26 | "created_at": "2016-09-08T11:55:58+0200", | ||
27 | "updated_at": "2016-09-08T11:57:16+0200", | ||
24 | "is_starred": false, | 28 | "is_starred": false, |
25 | "content": "<p>Pour un sommet sur les réfugiés qui devait se concentrer sur des <em>«mesures opérationnelles immédiates»</em> dans les Balkans, la réunion, dimanche à Bruxelles, de 11 chefs d’Etat et de gouvernement, dont 8 Européens, a été agitée. Dès leur arrivée, Viktor Orbán (Hongrie) et Aléxis Tsípras (Grèce) se sont jeté des anathèmes. Le Premier ministre grec a dénoncé l’attitude <em>«not in my backyard»</em> (pas de ça chez moi) de certains Etats européens, alors que son pays est montré du doigt par d’autres dirigeants, dont Orbán : ils reprochent à la Grèce de ne pas suffisamment contrôler ses frontières avec la Turquie et ne pas montrer assez de zèle dans l’enregistrement des demandeurs d’asile.</p>\n<p>Le sommet, convoqué par la Commission européenne, sur suggestion de l’Allemagne, aura au moins permis à ces 11 Etats – Autriche, Bulgarie, Croatie, Allemagne, Grèce, Hongrie, Roumanie, Slovénie côté européen, et 3 pays «non UE», Albanie, Macédoine et Serbie – de discuter ensemble.</p>\n<h3>400 policiers européens en Slovénie</h3>\n<p>L’objectif, rappelé par Angela Merkel, était de trouver une <em>«réponse coordonnée»</em> à la crise des réfugiés. Quelques mesures ont été annoncées : 100 000 places d’accueil seront créées, dont 50 000 en Grèce, et le reste le long de la route des Balkans. 400 officiers de police de pays européens partiront en Slovénie, actuellement submergée, pour aider au contrôle des frontières. Frontex, l’agence européenne de surveillance des frontières, s’impliquera aux frontières gréco-macédonienne et gréco-albanaise pour des contrôles et identifications.</p>\n<p>Ce sommet est intervenu dans un contexte de fortes tensions, marqué par des fermetures de frontières bloquant les réfugiés dans des zones tampon. Ces obstacles ont été partiellement levés ces derniers jours, les autorités tentant d’organiser un «corridor» informel vers l’Allemagne, qui pourtant durcit sa politique d’accueil et souhaite désormais ralentir le flux. Mais la situation des réfugiés est catastrophique. L’ONG Human Rights Watch craint que des réfugiés ne meurent dans les Balkans. Des groupes de centaines, voire de milliers de personnes, bloqués près des postes-frontières, se retrouvent dans des conditions humanitaires intenables.</p>\n<p>Depuis mi-septembre, 250 000 personnes ont traversé les Balkans. En une semaine, la Slovénie a vu 60 000 réfugiés fouler le sol de son territoire. Dimanche, 15 000 personnes ont transité en Slovénie.</p>\n<h3>Des zones tampon</h3>\n<p>L’enjeu principal du sommet, aux yeux de nombreux Etats de l’Union européenne, était aussi que les pays des Balkans <em>«prennent leur part»</em> face à la crise : qu’ils accueillent et enregistrent davantage de réfugiés. Ces Etats craignent que l’Autriche ou l’Allemagne ne ferment leurs frontières et fassent de leurs pays des <em>«zones tampon»</em>, comme s’en inquiétait Boyko Borissov, Premier ministre bulgare.</p>\n<p><em>« Aujourd’hui, plusieurs Etats du nord de l’Europe veulent que l’on enregistre les migrants puis que l’on détermine leur éligibilité au statut de réfugié,</em> explique Marc Pierini, du think tank Carnegie Europe. <em>La difficulté, c’est que les gens sont en mouvement. Pour le faire, il faut se poser quelque part. La crainte des pays intermédiaires, donc ceux des Balkans, est qu’on enregistre ces personnes sur leur territoire et qu’ils soient contraints de rester sur leur sol. Donc les pays des Balkans ne sont pas désireux d’accueillir ces réfugiés et ces derniers veulent avancer.»</em></p>\n<p>Le sommet a élaboré quelques principes. L’idée générale est de rendre effective la «logique de hotspot» : un enregistrement des demandeurs d’asile à leur point d’entrée dans l’Union européenne, suivi de l’expulsion de ceux qui ne correspondraient pas aux critères de la Convention de Genève, et la répartition des autres, via le mécanisme de relocalisation.</p>\n<p>Dans ce cadre, l’enregistrement des demandeurs d’asile est un élément clé. <em>«Pas d’enregistrement, pas de droit»</em>, a prévenu le président de la Commission européenne, Jean-Claude Juncker, dimanche soir. Les Etats ont tenu à rappeler que les migrants qui refusent de demander l’asile à la frontière peuvent se voir refuser l’entrée dans un pays.</p>\n<p>Et les Etats <em>«décourageront les mouvements de réfugiés»</em> de frontière en frontière. La politique consistant à laisser passer les migrants vers un autre pays est officiellement jugée <em>«inacceptable»</em>.</p>\n<h3>Se jeter dans la gueule du loup</h3>\n<p>Voilà pour la théorie. En pratique, la relocalisation ne devrait concerner que 160 000 réfugiés en deux ans, alors que près de 700 000 personnes sont arrivées en Europe depuis le début de l’année. De plus, les Etats ne jouent pas le jeu. La semaine passée, seules 854 places de relocalisation avaient été proposées.</p>\n<p>Dans ce contexte, il est probable que les Etats des Balkans ne s’impliqueront pas outre mesure dans les solutions proposées, craignant de devoir «garder» les réfugiés alors que l’Union européenne tarde à mettre en œuvre leur répartition.</p>\n<p>Quant aux réfugiés, ils préfèrent traverser les frontières par eux-mêmes, plutôt que de se jeter dans ces «hotspots», considérés comme la gueule du loup.</p>\n<a itemprop=\"name\" href=\"http://www.liberation.fr/auteur/15743-cedric-vallet\">Cédric Vallet</a>", | 29 | "content": "<p>Pour un sommet sur les réfugiés qui devait se concentrer sur des <em>«mesures opérationnelles immédiates»</em> dans les Balkans, la réunion, dimanche à Bruxelles, de 11 chefs d’Etat et de gouvernement, dont 8 Européens, a été agitée. Dès leur arrivée, Viktor Orbán (Hongrie) et Aléxis Tsípras (Grèce) se sont jeté des anathèmes. Le Premier ministre grec a dénoncé l’attitude <em>«not in my backyard»</em> (pas de ça chez moi) de certains Etats européens, alors que son pays est montré du doigt par d’autres dirigeants, dont Orbán : ils reprochent à la Grèce de ne pas suffisamment contrôler ses frontières avec la Turquie et ne pas montrer assez de zèle dans l’enregistrement des demandeurs d’asile.</p>\n<p>Le sommet, convoqué par la Commission européenne, sur suggestion de l’Allemagne, aura au moins permis à ces 11 Etats – Autriche, Bulgarie, Croatie, Allemagne, Grèce, Hongrie, Roumanie, Slovénie côté européen, et 3 pays «non UE», Albanie, Macédoine et Serbie – de discuter ensemble.</p>\n<h3>400 policiers européens en Slovénie</h3>\n<p>L’objectif, rappelé par Angela Merkel, était de trouver une <em>«réponse coordonnée»</em> à la crise des réfugiés. Quelques mesures ont été annoncées : 100 000 places d’accueil seront créées, dont 50 000 en Grèce, et le reste le long de la route des Balkans. 400 officiers de police de pays européens partiront en Slovénie, actuellement submergée, pour aider au contrôle des frontières. Frontex, l’agence européenne de surveillance des frontières, s’impliquera aux frontières gréco-macédonienne et gréco-albanaise pour des contrôles et identifications.</p>\n<p>Ce sommet est intervenu dans un contexte de fortes tensions, marqué par des fermetures de frontières bloquant les réfugiés dans des zones tampon. Ces obstacles ont été partiellement levés ces derniers jours, les autorités tentant d’organiser un «corridor» informel vers l’Allemagne, qui pourtant durcit sa politique d’accueil et souhaite désormais ralentir le flux. Mais la situation des réfugiés est catastrophique. L’ONG Human Rights Watch craint que des réfugiés ne meurent dans les Balkans. Des groupes de centaines, voire de milliers de personnes, bloqués près des postes-frontières, se retrouvent dans des conditions humanitaires intenables.</p>\n<p>Depuis mi-septembre, 250 000 personnes ont traversé les Balkans. En une semaine, la Slovénie a vu 60 000 réfugiés fouler le sol de son territoire. Dimanche, 15 000 personnes ont transité en Slovénie.</p>\n<h3>Des zones tampon</h3>\n<p>L’enjeu principal du sommet, aux yeux de nombreux Etats de l’Union européenne, était aussi que les pays des Balkans <em>«prennent leur part»</em> face à la crise : qu’ils accueillent et enregistrent davantage de réfugiés. Ces Etats craignent que l’Autriche ou l’Allemagne ne ferment leurs frontières et fassent de leurs pays des <em>«zones tampon»</em>, comme s’en inquiétait Boyko Borissov, Premier ministre bulgare.</p>\n<p><em>« Aujourd’hui, plusieurs Etats du nord de l’Europe veulent que l’on enregistre les migrants puis que l’on détermine leur éligibilité au statut de réfugié,</em> explique Marc Pierini, du think tank Carnegie Europe. <em>La difficulté, c’est que les gens sont en mouvement. Pour le faire, il faut se poser quelque part. La crainte des pays intermédiaires, donc ceux des Balkans, est qu’on enregistre ces personnes sur leur territoire et qu’ils soient contraints de rester sur leur sol. Donc les pays des Balkans ne sont pas désireux d’accueillir ces réfugiés et ces derniers veulent avancer.»</em></p>\n<p>Le sommet a élaboré quelques principes. L’idée générale est de rendre effective la «logique de hotspot» : un enregistrement des demandeurs d’asile à leur point d’entrée dans l’Union européenne, suivi de l’expulsion de ceux qui ne correspondraient pas aux critères de la Convention de Genève, et la répartition des autres, via le mécanisme de relocalisation.</p>\n<p>Dans ce cadre, l’enregistrement des demandeurs d’asile est un élément clé. <em>«Pas d’enregistrement, pas de droit»</em>, a prévenu le président de la Commission européenne, Jean-Claude Juncker, dimanche soir. Les Etats ont tenu à rappeler que les migrants qui refusent de demander l’asile à la frontière peuvent se voir refuser l’entrée dans un pays.</p>\n<p>Et les Etats <em>«décourageront les mouvements de réfugiés»</em> de frontière en frontière. La politique consistant à laisser passer les migrants vers un autre pays est officiellement jugée <em>«inacceptable»</em>.</p>\n<h3>Se jeter dans la gueule du loup</h3>\n<p>Voilà pour la théorie. En pratique, la relocalisation ne devrait concerner que 160 000 réfugiés en deux ans, alors que près de 700 000 personnes sont arrivées en Europe depuis le début de l’année. De plus, les Etats ne jouent pas le jeu. La semaine passée, seules 854 places de relocalisation avaient été proposées.</p>\n<p>Dans ce contexte, il est probable que les Etats des Balkans ne s’impliqueront pas outre mesure dans les solutions proposées, craignant de devoir «garder» les réfugiés alors que l’Union européenne tarde à mettre en œuvre leur répartition.</p>\n<p>Quant aux réfugiés, ils préfèrent traverser les frontières par eux-mêmes, plutôt que de se jeter dans ces «hotspots», considérés comme la gueule du loup.</p>\n<a itemprop=\"name\" href=\"http://www.liberation.fr/auteur/15743-cedric-vallet\">Cédric Vallet</a>", |
26 | "mimetype": "", | 30 | "mimetype": "", |
@@ -34,6 +38,8 @@ | |||
34 | "title": "No title found", | 38 | "title": "No title found", |
35 | "url": "http://news.nationalgeographic.com/2016/02/160211-albatrosses-mothers-babies-animals-science/&sf20739758=1", | 39 | "url": "http://news.nationalgeographic.com/2016/02/160211-albatrosses-mothers-babies-animals-science/&sf20739758=1", |
36 | "is_archived": false, | 40 | "is_archived": false, |
41 | "created_at": "2016-09-08T11:55:58+0200", | ||
42 | "updated_at": "2016-09-08T11:57:16+0200", | ||
37 | "is_starred": true, | 43 | "is_starred": true, |
38 | "content": "Oh, what a shame, no content", | 44 | "content": "Oh, what a shame, no content", |
39 | "mimetype": "", | 45 | "mimetype": "", |
@@ -44,6 +50,8 @@ | |||
44 | }, | 50 | }, |
45 | { | 51 | { |
46 | "is_archived": 0, | 52 | "is_archived": 0, |
53 | "created_at": "2016-09-08T11:55:58+0200", | ||
54 | "updated_at": "2016-09-08T11:57:16+0200", | ||
47 | "is_starred": 0, | 55 | "is_starred": 0, |
48 | "id": 612, | 56 | "id": 612, |
49 | "title": "Échecs", | 57 | "title": "Échecs", |
@@ -58,6 +66,8 @@ | |||
58 | }, | 66 | }, |
59 | { | 67 | { |
60 | "is_archived": 0, | 68 | "is_archived": 0, |
69 | "created_at": "2016-09-08T11:55:58+0200", | ||
70 | "updated_at": "2016-09-08T11:57:16+0200", | ||
61 | "is_starred": 0, | 71 | "is_starred": 0, |
62 | "id": 608, | 72 | "id": 608, |
63 | "title": "90% des dossiers médicaux des Coréens du sud vendus à des entreprises privées - ZATAZ", | 73 | "title": "90% des dossiers médicaux des Coréens du sud vendus à des entreprises privées - ZATAZ", |
@@ -73,6 +83,8 @@ | |||
73 | }, | 83 | }, |
74 | { | 84 | { |
75 | "is_archived": 0, | 85 | "is_archived": 0, |
86 | "created_at": "2016-09-08T11:55:58+0200", | ||
87 | "updated_at": "2016-09-08T11:57:16+0200", | ||
76 | "is_starred": 0, | 88 | "is_starred": 0, |
77 | "id": 606, | 89 | "id": 606, |
78 | "title": "Mass Surveillance As Art", | 90 | "title": "Mass Surveillance As Art", |
@@ -87,6 +99,8 @@ | |||
87 | }, | 99 | }, |
88 | { | 100 | { |
89 | "is_archived": 0, | 101 | "is_archived": 0, |
102 | "created_at": "2016-09-08T11:55:58+0200", | ||
103 | "updated_at": "2016-09-08T11:57:16+0200", | ||
90 | "is_starred": 0, | 104 | "is_starred": 0, |
91 | "id": 605, | 105 | "id": 605, |
92 | "title": "What David Cameron did to the pig, his party is now doing to the country", | 106 | "title": "What David Cameron did to the pig, his party is now doing to the country", |
@@ -102,6 +116,8 @@ | |||
102 | }, | 116 | }, |
103 | { | 117 | { |
104 | "is_archived": 1, | 118 | "is_archived": 1, |
119 | "created_at": "2016-09-08T11:55:58+0200", | ||
120 | "updated_at": "2016-09-08T11:57:16+0200", | ||
105 | "is_starred": 0, | 121 | "is_starred": 0, |
106 | "id": 604, | 122 | "id": 604, |
107 | "title": "CLICK HERE to support 2016 CES Winner, Revolutionary Auto-Tracking Robot", | 123 | "title": "CLICK HERE to support 2016 CES Winner, Revolutionary Auto-Tracking Robot", |
@@ -116,6 +132,8 @@ | |||
116 | }, | 132 | }, |
117 | { | 133 | { |
118 | "is_archived": 0, | 134 | "is_archived": 0, |
135 | "created_at": "2016-09-08T11:55:58+0200", | ||
136 | "updated_at": "2016-09-08T11:57:16+0200", | ||
119 | "is_starred": 1, | 137 | "is_starred": 1, |
120 | "id": 603, | 138 | "id": 603, |
121 | "title": "No title found", | 139 | "title": "No title found", |
@@ -129,6 +147,8 @@ | |||
129 | }, | 147 | }, |
130 | { | 148 | { |
131 | "is_archived": 1, | 149 | "is_archived": 1, |
150 | "created_at": "2016-09-08T11:55:58+0200", | ||
151 | "updated_at": "2016-09-08T11:57:16+0200", | ||
132 | "is_starred": 0, | 152 | "is_starred": 0, |
133 | "id": 602, | 153 | "id": 602, |
134 | "title": "Présentation d'Arduino - Tuto Arduino - Le blog d'Eskimon", | 154 | "title": "Présentation d'Arduino - Tuto Arduino - Le blog d'Eskimon", |
@@ -144,6 +164,8 @@ | |||
144 | }, | 164 | }, |
145 | { | 165 | { |
146 | "is_archived": 1, | 166 | "is_archived": 1, |
167 | "created_at": "2016-09-08T11:55:58+0200", | ||
168 | "updated_at": "2016-09-08T11:57:16+0200", | ||
147 | "is_starred": 0, | 169 | "is_starred": 0, |
148 | "id": 543, | 170 | "id": 543, |
149 | "title": "Lenovo ThinkPad X1 Carbon Ultrabook Review", | 171 | "title": "Lenovo ThinkPad X1 Carbon Ultrabook Review", |
@@ -159,6 +181,8 @@ | |||
159 | }, | 181 | }, |
160 | { | 182 | { |
161 | "is_archived": 0, | 183 | "is_archived": 0, |
184 | "created_at": "2016-09-08T11:55:58+0200", | ||
185 | "updated_at": "2016-09-08T11:57:16+0200", | ||
162 | "is_starred": 0, | 186 | "is_starred": 0, |
163 | "id": 541, | 187 | "id": 541, |
164 | "title": "Visitons le Château de Landsberg !", | 188 | "title": "Visitons le Château de Landsberg !", |
@@ -174,6 +198,8 @@ | |||
174 | }, | 198 | }, |
175 | { | 199 | { |
176 | "is_archived": 1, | 200 | "is_archived": 1, |
201 | "created_at": "2016-09-08T11:55:58+0200", | ||
202 | "updated_at": "2016-09-08T11:57:16+0200", | ||
177 | "is_starred": 0, | 203 | "is_starred": 0, |
178 | "id": 454, | 204 | "id": 454, |
179 | "title": "Contrer les stéréotypes par les livres : “C'est dès l'enfance qu'ils se construisent”", | 205 | "title": "Contrer les stéréotypes par les livres : “C'est dès l'enfance qu'ils se construisent”", |
@@ -189,6 +215,8 @@ | |||
189 | }, | 215 | }, |
190 | { | 216 | { |
191 | "is_archived": 1, | 217 | "is_archived": 1, |
218 | "created_at": "2016-09-08T11:55:58+0200", | ||
219 | "updated_at": "2016-09-08T11:57:16+0200", | ||
192 | "is_starred": 0, | 220 | "is_starred": 0, |
193 | "id": 99, | 221 | "id": 99, |
194 | "title": "[ROM][6.0.1][Layers][N5] TipsyOS official builds {UBER TCs}", | 222 | "title": "[ROM][6.0.1][Layers][N5] TipsyOS official builds {UBER TCs}", |
@@ -204,6 +232,8 @@ | |||
204 | }, | 232 | }, |
205 | { | 233 | { |
206 | "is_archived": 0, | 234 | "is_archived": 0, |
235 | "created_at": "2016-09-08T11:55:58+0200", | ||
236 | "updated_at": "2016-09-08T11:57:16+0200", | ||
207 | "is_starred": 0, | 237 | "is_starred": 0, |
208 | "id": 98, | 238 | "id": 98, |
209 | "title": "Top 15 Podcasts All Web Developers Should Follow - Envato Tuts+ Code Article", | 239 | "title": "Top 15 Podcasts All Web Developers Should Follow - Envato Tuts+ Code Article", |
@@ -218,6 +248,8 @@ | |||
218 | }, | 248 | }, |
219 | { | 249 | { |
220 | "is_archived": 1, | 250 | "is_archived": 1, |
251 | "created_at": "2016-09-08T11:55:58+0200", | ||
252 | "updated_at": "2016-09-08T11:57:16+0200", | ||
221 | "is_starred": 0, | 253 | "is_starred": 0, |
222 | "id": 97, | 254 | "id": 97, |
223 | "title": "University of Mississippi", | 255 | "title": "University of Mississippi", |
@@ -232,6 +264,8 @@ | |||
232 | }, | 264 | }, |
233 | { | 265 | { |
234 | "is_archived": 1, | 266 | "is_archived": 1, |
267 | "created_at": "2016-09-08T11:55:58+0200", | ||
268 | "updated_at": "2016-09-08T11:57:16+0200", | ||
235 | "is_starred": 0, | 269 | "is_starred": 0, |
236 | "id": 96, | 270 | "id": 96, |
237 | "title": "FinnChristiansen.de Jetzt Dank Let’s Encrypt Per HTTPS Erreichbar", | 271 | "title": "FinnChristiansen.de Jetzt Dank Let’s Encrypt Per HTTPS Erreichbar", |
@@ -246,6 +280,8 @@ | |||
246 | }, | 280 | }, |
247 | { | 281 | { |
248 | "is_archived": 1, | 282 | "is_archived": 1, |
283 | "created_at": "2016-09-08T11:55:58+0200", | ||
284 | "updated_at": "2016-09-08T11:57:16+0200", | ||
249 | "is_starred": 0, | 285 | "is_starred": 0, |
250 | "id": 82, | 286 | "id": 82, |
251 | "title": "Le développeur et l'ingénierie logicielle", | 287 | "title": "Le développeur et l'ingénierie logicielle", |
@@ -259,6 +295,8 @@ | |||
259 | }, | 295 | }, |
260 | { | 296 | { |
261 | "is_archived": 1, | 297 | "is_archived": 1, |
298 | "created_at": "2016-09-08T11:55:58+0200", | ||
299 | "updated_at": "2016-09-08T11:57:16+0200", | ||
262 | "is_starred": 0, | 300 | "is_starred": 0, |
263 | "id": 78, | 301 | "id": 78, |
264 | "title": "The Role of Methylation in Gene Expression", | 302 | "title": "The Role of Methylation in Gene Expression", |
@@ -273,6 +311,8 @@ | |||
273 | }, | 311 | }, |
274 | { | 312 | { |
275 | "is_archived": 1, | 313 | "is_archived": 1, |
314 | "created_at": "2016-09-08T11:55:58+0200", | ||
315 | "updated_at": "2016-09-08T11:57:16+0200", | ||
276 | "is_starred": 0, | 316 | "is_starred": 0, |
277 | "id": 53, | 317 | "id": 53, |
278 | "title": "E-Mail-Adresse kostenlos, FreeMail, De-Mail & Nachrichten", | 318 | "title": "E-Mail-Adresse kostenlos, FreeMail, De-Mail & Nachrichten", |
@@ -287,6 +327,8 @@ | |||
287 | }, | 327 | }, |
288 | { | 328 | { |
289 | "is_archived": 1, | 329 | "is_archived": 1, |
330 | "created_at": "2016-09-08T11:55:58+0200", | ||
331 | "updated_at": "2016-09-08T11:57:16+0200", | ||
290 | "is_starred": 0, | 332 | "is_starred": 0, |
291 | "id": 48, | 333 | "id": 48, |
292 | "title": "OpenSSH Server on Arch Linux | DominicM test", | 334 | "title": "OpenSSH Server on Arch Linux | DominicM test", |
@@ -302,6 +344,8 @@ | |||
302 | }, | 344 | }, |
303 | { | 345 | { |
304 | "is_archived": 1, | 346 | "is_archived": 1, |
347 | "created_at": "2016-09-08T11:55:58+0200", | ||
348 | "updated_at": "2016-09-08T11:57:16+0200", | ||
305 | "is_starred": 0, | 349 | "is_starred": 0, |
306 | "id": 39, | 350 | "id": 39, |
307 | "title": "Site Moved | Site Help", | 351 | "title": "Site Moved | Site Help", |
@@ -316,6 +360,8 @@ | |||
316 | }, | 360 | }, |
317 | { | 361 | { |
318 | "is_archived": 1, | 362 | "is_archived": 1, |
363 | "created_at": "2016-09-08T11:55:58+0200", | ||
364 | "updated_at": "2016-09-08T11:57:16+0200", | ||
319 | "is_starred": 0, | 365 | "is_starred": 0, |
320 | "id": 38, | 366 | "id": 38, |
321 | "title": "#Maroc : le stylo anti-pédophiles EAGLE d’AMESYS est moins bien configuré que les faux-lowers Twitter du roi Mohammed VI", | 367 | "title": "#Maroc : le stylo anti-pédophiles EAGLE d’AMESYS est moins bien configuré que les faux-lowers Twitter du roi Mohammed VI", |
@@ -331,6 +377,8 @@ | |||
331 | }, | 377 | }, |
332 | { | 378 | { |
333 | "is_archived": 1, | 379 | "is_archived": 1, |
380 | "created_at": "2016-09-08T11:55:58+0200", | ||
381 | "updated_at": "2016-09-08T11:57:16+0200", | ||
334 | "is_starred": 0, | 382 | "is_starred": 0, |
335 | "id": 3, | 383 | "id": 3, |
336 | "title": "Simple Cloud Infrastructure for Developers", | 384 | "title": "Simple Cloud Infrastructure for Developers", |