mirror of
https://github.com/Athou/commafeed.git
synced 2026-11-04 19:34:47 +00:00
restore empty categories when importing OPML files (#2364)
This commit is contained in:
@@ -47,7 +47,11 @@ public class OPMLImporter {
|
|||||||
|
|
||||||
private void handleOutline(User user, Outline outline, FeedCategory parent, int position) {
|
private void handleOutline(User user, Outline outline, FeedCategory parent, int position) {
|
||||||
List<Outline> children = outline.getChildren();
|
List<Outline> children = outline.getChildren();
|
||||||
if (CollectionUtils.isNotEmpty(children)) {
|
// an outline without children is an empty category, unless it describes a feed or another type of content
|
||||||
|
boolean isCategory =
|
||||||
|
CollectionUtils.isNotEmpty(children)
|
||||||
|
|| StringUtils.isBlank(outline.getXmlUrl()) && StringUtils.isBlank(outline.getType());
|
||||||
|
if (isCategory) {
|
||||||
String name = FeedUtils.truncate(outline.getText(), 128);
|
String name = FeedUtils.truncate(outline.getText(), 128);
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
name = FeedUtils.truncate(outline.getTitle(), 128);
|
name = FeedUtils.truncate(outline.getTitle(), 128);
|
||||||
|
|||||||
@@ -1,21 +1,39 @@
|
|||||||
package com.commafeed.backend.opml;
|
package com.commafeed.backend.opml;
|
||||||
|
|
||||||
import com.commafeed.backend.dao.FeedCategoryDAO;
|
import com.commafeed.backend.dao.FeedCategoryDAO;
|
||||||
|
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||||
import com.commafeed.backend.feed.parser.XMLCleaner;
|
import com.commafeed.backend.feed.parser.XMLCleaner;
|
||||||
import com.commafeed.backend.model.FeedCategory;
|
import com.commafeed.backend.model.FeedCategory;
|
||||||
import com.commafeed.backend.model.User;
|
import com.commafeed.backend.model.User;
|
||||||
import com.commafeed.backend.service.FeedSubscriptionService;
|
import com.commafeed.backend.service.FeedSubscriptionService;
|
||||||
import com.rometools.rome.io.FeedException;
|
import com.rometools.rome.io.FeedException;
|
||||||
|
import com.rometools.rome.io.WireFeedOutput;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
class OPMLImporterTest {
|
class OPMLImporterTest {
|
||||||
|
|
||||||
|
private final XMLCleaner xmlCleaner = Mockito.mock(XMLCleaner.class);
|
||||||
|
private final FeedCategoryDAO feedCategoryDAO = Mockito.mock(FeedCategoryDAO.class);
|
||||||
|
private final FeedSubscriptionService feedSubscriptionService = Mockito.mock(FeedSubscriptionService.class);
|
||||||
|
private final OPMLImporter importer = new OPMLImporter(xmlCleaner, feedCategoryDAO, feedSubscriptionService);
|
||||||
|
private final User user = new User();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void init() {
|
||||||
|
Mockito.when(xmlCleaner.clean(Mockito.anyString())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testOpmlV10() throws IOException, IllegalArgumentException, FeedException {
|
void testOpmlV10() throws IOException, IllegalArgumentException, FeedException {
|
||||||
testOpmlVersion("/opml/opml_v1.0.xml");
|
testOpmlVersion("/opml/opml_v1.0.xml");
|
||||||
@@ -37,15 +55,8 @@ class OPMLImporterTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void testOpmlVersion(String fileName) throws IOException, IllegalArgumentException, FeedException {
|
private void testOpmlVersion(String fileName) throws IOException, IllegalArgumentException, FeedException {
|
||||||
XMLCleaner xmlCleaner = Mockito.mock(XMLCleaner.class);
|
|
||||||
FeedCategoryDAO feedCategoryDAO = Mockito.mock(FeedCategoryDAO.class);
|
|
||||||
FeedSubscriptionService feedSubscriptionService = Mockito.mock(FeedSubscriptionService.class);
|
|
||||||
User user = Mockito.mock(User.class);
|
|
||||||
|
|
||||||
Mockito.when(xmlCleaner.clean(Mockito.anyString())).thenAnswer(invocation -> invocation.getArgument(0));
|
|
||||||
String xml = IOUtils.toString(getClass().getResourceAsStream(fileName), StandardCharsets.UTF_8);
|
String xml = IOUtils.toString(getClass().getResourceAsStream(fileName), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
OPMLImporter importer = new OPMLImporter(xmlCleaner, feedCategoryDAO, feedSubscriptionService);
|
|
||||||
importer.importOpml(user, xml);
|
importer.importOpml(user, xml);
|
||||||
|
|
||||||
Mockito.verify(feedSubscriptionService)
|
Mockito.verify(feedSubscriptionService)
|
||||||
@@ -56,4 +67,49 @@ class OPMLImporterTest {
|
|||||||
Mockito.any(FeedCategory.class),
|
Mockito.any(FeedCategory.class),
|
||||||
Mockito.anyInt());
|
Mockito.anyInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEmptyCategoriesRoundTrip() throws FeedException {
|
||||||
|
FeedCategory root = newCategory("root", null);
|
||||||
|
FeedCategory parent = newCategory("parent", null);
|
||||||
|
parent.setId(1L);
|
||||||
|
FeedCategory child = newCategory("child", parent);
|
||||||
|
Mockito.when(feedCategoryDAO.findAll(user)).thenReturn(Arrays.asList(root, parent, child));
|
||||||
|
|
||||||
|
OPMLExporter exporter = new OPMLExporter(feedCategoryDAO, Mockito.mock(FeedSubscriptionDAO.class));
|
||||||
|
importer.importOpml(user, new WireFeedOutput().outputString(exporter.export(user)));
|
||||||
|
|
||||||
|
ArgumentCaptor<FeedCategory> captor = ArgumentCaptor.forClass(FeedCategory.class);
|
||||||
|
Mockito.verify(feedCategoryDAO, Mockito.times(3)).persist(captor.capture());
|
||||||
|
List<FeedCategory> imported = captor.getAllValues();
|
||||||
|
Assertions.assertEquals(
|
||||||
|
List.of("root", "parent", "child"), imported.stream().map(FeedCategory::getName).toList());
|
||||||
|
Assertions.assertNull(imported.get(0).getParent());
|
||||||
|
Assertions.assertNull(imported.get(1).getParent());
|
||||||
|
Assertions.assertSame(imported.get(1), imported.get(2).getParent());
|
||||||
|
Mockito.verifyNoInteractions(feedSubscriptionService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testOutlineWithTypeAndNoFeedUrlIsNotACategory() throws FeedException {
|
||||||
|
String xml =
|
||||||
|
"""
|
||||||
|
<opml version="1.0">
|
||||||
|
<body>
|
||||||
|
<outline type="link" text="bookmark"/>
|
||||||
|
</body>
|
||||||
|
</opml>
|
||||||
|
""";
|
||||||
|
|
||||||
|
importer.importOpml(user, xml);
|
||||||
|
|
||||||
|
Mockito.verify(feedCategoryDAO, Mockito.never()).persist(Mockito.any(FeedCategory.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private FeedCategory newCategory(String name, FeedCategory parent) {
|
||||||
|
FeedCategory category = new FeedCategory();
|
||||||
|
category.setName(name);
|
||||||
|
category.setParent(parent);
|
||||||
|
return category;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user