1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 package org.mozilla.gecko.dlc.catalog;
7 
8 import android.support.v4.util.ArrayMap;
9 import android.support.v4.util.AtomicFile;
10 
11 import org.junit.Assert;
12 import org.junit.Assume;
13 import org.junit.Test;
14 import org.junit.runner.RunWith;
15 import org.mozilla.gecko.AppConstants;
16 import org.mozilla.gecko.background.testhelpers.TestRunner;
17 
18 import java.io.FileNotFoundException;
19 import java.io.FileOutputStream;
20 
21 import static org.mockito.Mockito.doReturn;
22 import static org.mockito.Mockito.doThrow;
23 import static org.mockito.Mockito.mock;
24 import static org.mockito.Mockito.never;
25 import static org.mockito.Mockito.spy;
26 import static org.mockito.Mockito.verify;
27 
28 @RunWith(TestRunner.class)
29 public class TestDownloadContentCatalog {
30     /**
31      * Scenario: Create a new, fresh catalog.
32      *
33      * Verify that:
34      *  * Catalog has not changed
35      *  * Unchanged catalog will not be saved to disk
36      */
37     @Test
testUntouchedCatalogHasNotChangedAndWillNotBePersisted()38     public void testUntouchedCatalogHasNotChangedAndWillNotBePersisted() throws Exception {
39         AtomicFile file = mock(AtomicFile.class);
40         doReturn("{content:[]}".getBytes("UTF-8")).when(file).readFully();
41 
42         DownloadContentCatalog catalog = spy(new DownloadContentCatalog(file));
43         catalog.loadFromDisk();
44 
45         Assert.assertFalse("Catalog has not changed", catalog.hasCatalogChanged());
46 
47         catalog.writeToDisk();
48 
49         Assert.assertFalse("Catalog has not changed", catalog.hasCatalogChanged());
50 
51         verify(file, never()).startWrite();
52     }
53 
54     /**
55      * Scenario: Create a new, fresh catalog.
56      *
57      * Verify that:
58      *  * Catalog is created empty
59      */
60     @Test
testCatalogIsBootstrappedIfFileDoesNotExist()61     public void testCatalogIsBootstrappedIfFileDoesNotExist() throws Exception {
62         // The catalog is only bootstrapped if fonts are excluded from the build. If this is a build
63         // with fonts included then ignore this test.
64         Assume.assumeTrue("Fonts are excluded from build", AppConstants.MOZ_ANDROID_EXCLUDE_FONTS);
65 
66         AtomicFile file = mock(AtomicFile.class);
67         doThrow(FileNotFoundException.class).when(file).readFully();
68 
69         DownloadContentCatalog catalog = spy(new DownloadContentCatalog(file));
70         catalog.loadFromDisk();
71 
72         Assert.assertEquals("Catalog is empty", 0, catalog.getContentToStudy().size());
73     }
74 
75     /**
76      * Scenario: Schedule downloading an item from the catalog.
77      *
78      * Verify that:
79      *  * Catalog has changed
80      */
81     @Test
testCatalogHasChangedWhenDownloadIsScheduled()82     public void testCatalogHasChangedWhenDownloadIsScheduled() throws Exception {
83         DownloadContentCatalog catalog = spy(new DownloadContentCatalog(mock(AtomicFile.class)));
84         DownloadContent content = new DownloadContentBuilder().build();
85         catalog.onCatalogLoaded(createMapOfContent(content));
86 
87         Assert.assertFalse("Catalog has not changed", catalog.hasCatalogChanged());
88 
89         catalog.scheduleDownload(content);
90 
91         Assert.assertTrue("Catalog has changed", catalog.hasCatalogChanged());
92     }
93 
94     /**
95      * Scenario: Mark an item in the catalog as downloaded.
96      *
97      * Verify that:
98      *  * Catalog has changed
99      */
100     @Test
testCatalogHasChangedWhenContentIsDownloaded()101     public void testCatalogHasChangedWhenContentIsDownloaded() throws Exception {
102         DownloadContentCatalog catalog = spy(new DownloadContentCatalog(mock(AtomicFile.class)));
103         DownloadContent content = new DownloadContentBuilder().build();
104         catalog.onCatalogLoaded(createMapOfContent(content));
105 
106         Assert.assertFalse("Catalog has not changed", catalog.hasCatalogChanged());
107 
108         catalog.markAsDownloaded(content);
109 
110         Assert.assertTrue("Catalog has changed", catalog.hasCatalogChanged());
111     }
112 
113     /**
114      * Scenario: Mark an item in the catalog as permanently failed.
115      *
116      * Verify that:
117      *  * Catalog has changed
118      */
119     @Test
testCatalogHasChangedIfDownloadHasFailedPermanently()120     public void testCatalogHasChangedIfDownloadHasFailedPermanently() throws Exception {
121         DownloadContentCatalog catalog = spy(new DownloadContentCatalog(mock(AtomicFile.class)));
122         DownloadContent content = new DownloadContentBuilder().build();
123         catalog.onCatalogLoaded(createMapOfContent(content));
124 
125         Assert.assertFalse("Catalog has not changed", catalog.hasCatalogChanged());
126 
127         catalog.markAsPermanentlyFailed(content);
128 
129         Assert.assertTrue("Catalog has changed", catalog.hasCatalogChanged());
130     }
131 
132     /**
133      * Scenario: A changed catalog is written to disk.
134      *
135      * Verify that:
136      *  * Before write: Catalog has changed
137      *  * After write: Catalog has not changed.
138      */
139     @Test
testCatalogHasNotChangedAfterWritingToDisk()140     public void testCatalogHasNotChangedAfterWritingToDisk() throws Exception {
141         AtomicFile file = mock(AtomicFile.class);
142         doReturn(mock(FileOutputStream.class)).when(file).startWrite();
143 
144         DownloadContentCatalog catalog = spy(new DownloadContentCatalog(file));
145         DownloadContent content = new DownloadContentBuilder().build();
146         catalog.onCatalogLoaded(createMapOfContent(content));
147 
148         catalog.scheduleDownload(content);
149 
150         Assert.assertTrue("Catalog has changed", catalog.hasCatalogChanged());
151 
152         catalog.writeToDisk();
153 
154         Assert.assertFalse("Catalog has not changed", catalog.hasCatalogChanged());
155     }
156 
157     /**
158      * Scenario: A catalog with multiple items in different states.
159      *
160      * Verify that:
161      *  * getContentWithoutState(), getDownloadedContent() and getScheduledDownloads() returns
162      *    the correct items depenending on their state.
163      */
164     @Test
testContentClassification()165     public void testContentClassification() {
166         DownloadContentCatalog catalog = spy(new DownloadContentCatalog(mock(AtomicFile.class)));
167 
168         DownloadContent content1 = new DownloadContentBuilder().setId("A").setState(DownloadContent.STATE_NONE).build();
169         DownloadContent content2 = new DownloadContentBuilder().setId("B").setState(DownloadContent.STATE_NONE).build();
170         DownloadContent content3 = new DownloadContentBuilder().setId("C").setState(DownloadContent.STATE_SCHEDULED).build();
171         DownloadContent content4 = new DownloadContentBuilder().setId("D").setState(DownloadContent.STATE_SCHEDULED).build();
172         DownloadContent content5 = new DownloadContentBuilder().setId("E").setState(DownloadContent.STATE_SCHEDULED).build();
173         DownloadContent content6 = new DownloadContentBuilder().setId("F").setState(DownloadContent.STATE_DOWNLOADED).build();
174         DownloadContent content7 = new DownloadContentBuilder().setId("G").setState(DownloadContent.STATE_FAILED).build();
175         DownloadContent content8 = new DownloadContentBuilder().setId("H").setState(DownloadContent.STATE_UPDATED).build();
176         DownloadContent content9 = new DownloadContentBuilder().setId("I").setState(DownloadContent.STATE_DELETED).build();
177         DownloadContent content10 = new DownloadContentBuilder().setId("J").setState(DownloadContent.STATE_DELETED).build();
178 
179         catalog.onCatalogLoaded(createMapOfContent(content1, content2, content3, content4, content5, content6,
180                 content7, content8, content9, content10));
181 
182         Assert.assertTrue(catalog.hasScheduledDownloads());
183 
184         Assert.assertEquals(3, catalog.getContentToStudy().size());
185         Assert.assertEquals(1, catalog.getDownloadedContent().size());
186         Assert.assertEquals(3, catalog.getScheduledDownloads().size());
187         Assert.assertEquals(2, catalog.getContentToDelete().size());
188 
189         Assert.assertTrue(catalog.getContentToStudy().contains(content1));
190         Assert.assertTrue(catalog.getContentToStudy().contains(content2));
191         Assert.assertTrue(catalog.getContentToStudy().contains(content8));
192 
193         Assert.assertTrue(catalog.getDownloadedContent().contains(content6));
194 
195         Assert.assertTrue(catalog.getScheduledDownloads().contains(content3));
196         Assert.assertTrue(catalog.getScheduledDownloads().contains(content4));
197         Assert.assertTrue(catalog.getScheduledDownloads().contains(content5));
198 
199         Assert.assertTrue(catalog.getContentToDelete().contains(content9));
200         Assert.assertTrue(catalog.getContentToDelete().contains(content10));
201     }
202 
203     /**
204      * Scenario: Calling rememberFailure() on a catalog with varying values
205      */
206     @Test
testRememberingFailures()207     public void testRememberingFailures() {
208         DownloadContentCatalog catalog = new DownloadContentCatalog(mock(AtomicFile.class));
209         Assert.assertFalse(catalog.hasCatalogChanged());
210 
211         DownloadContent content = new DownloadContentBuilder().build();
212         Assert.assertEquals(0, content.getFailures());
213 
214         catalog.rememberFailure(content, 42);
215         Assert.assertEquals(1, content.getFailures());
216         Assert.assertTrue(catalog.hasCatalogChanged());
217 
218         catalog.rememberFailure(content, 42);
219         Assert.assertEquals(2, content.getFailures());
220 
221         // Failure counter is reset if different failure has been reported
222         catalog.rememberFailure(content, 23);
223         Assert.assertEquals(1, content.getFailures());
224 
225         // Failure counter is reset after successful download
226         catalog.markAsDownloaded(content);
227         Assert.assertEquals(0, content.getFailures());
228     }
229 
230     /**
231      * Scenario: Content has failed multiple times with the same failure type.
232      *
233      * Verify that:
234      *  * Content is marked as permanently failed
235      */
236     @Test
testContentWillBeMarkedAsPermanentlyFailedAfterMultipleFailures()237     public void testContentWillBeMarkedAsPermanentlyFailedAfterMultipleFailures() {
238         DownloadContentCatalog catalog = new DownloadContentCatalog(mock(AtomicFile.class));
239 
240         DownloadContent content = new DownloadContentBuilder().build();
241         Assert.assertEquals(DownloadContent.STATE_NONE, content.getState());
242 
243         for (int i = 0; i < 10; i++) {
244             catalog.rememberFailure(content, 42);
245 
246             Assert.assertEquals(i + 1, content.getFailures());
247             Assert.assertEquals(DownloadContent.STATE_NONE, content.getState());
248         }
249 
250         catalog.rememberFailure(content, 42);
251         Assert.assertEquals(10, content.getFailures());
252         Assert.assertEquals(DownloadContent.STATE_FAILED, content.getState());
253     }
254 
createMapOfContent(DownloadContent... content)255     private ArrayMap<String, DownloadContent> createMapOfContent(DownloadContent... content) {
256         ArrayMap<String, DownloadContent> map = new ArrayMap<>();
257         for (DownloadContent currentContent : content) {
258             map.put(currentContent.getId(), currentContent);
259         }
260         return map;
261     }
262 }
263