2026-03-02 13:31:54 +08:00
|
|
|
package com.mosquito.project.service;
|
|
|
|
|
|
|
|
|
|
import com.mosquito.project.config.PosterConfig;
|
|
|
|
|
import com.mosquito.project.persistence.entity.ShortLinkEntity;
|
|
|
|
|
import org.junit.jupiter.api.BeforeAll;
|
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
|
import org.mockito.Mockito;
|
|
|
|
|
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
|
import static org.mockito.ArgumentMatchers.anyString;
|
|
|
|
|
import static org.mockito.Mockito.when;
|
|
|
|
|
|
|
|
|
|
class PosterRenderServiceTest {
|
|
|
|
|
|
|
|
|
|
@BeforeAll
|
|
|
|
|
static void enableHeadlessMode() {
|
|
|
|
|
System.setProperty("java.awt.headless", "true");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void renderPosterHtml_includesElementsAndShortUrl() {
|
|
|
|
|
ShortLinkService shortLinkService = Mockito.mock(ShortLinkService.class);
|
|
|
|
|
ShortLinkEntity shortLink = new ShortLinkEntity();
|
|
|
|
|
shortLink.setCode("short123");
|
|
|
|
|
when(shortLinkService.create(anyString())).thenReturn(shortLink);
|
|
|
|
|
|
|
|
|
|
PosterConfig posterConfig = buildPosterConfig(buildHtmlElements());
|
|
|
|
|
PosterRenderService service = new PosterRenderService(posterConfig, shortLinkService);
|
|
|
|
|
|
|
|
|
|
String html = service.renderPosterHtml(10L, 20L, "custom");
|
|
|
|
|
|
|
|
|
|
assertTrue(html.contains("/r/short123"));
|
|
|
|
|
assertTrue(html.contains("data=%2Fr%2Fshort123"));
|
|
|
|
|
assertTrue(html.contains("Hello 10"));
|
|
|
|
|
assertTrue(html.contains("https://example.com/image.png"));
|
|
|
|
|
assertTrue(html.contains("立即加入"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void renderPoster_generatesPngBytes() {
|
|
|
|
|
ShortLinkService shortLinkService = Mockito.mock(ShortLinkService.class);
|
|
|
|
|
PosterConfig posterConfig = buildPosterConfig(buildImageElements());
|
|
|
|
|
PosterRenderService service = new PosterRenderService(posterConfig, shortLinkService);
|
|
|
|
|
|
|
|
|
|
byte[] bytes = service.renderPoster(11L, 22L, "custom");
|
|
|
|
|
|
|
|
|
|
assertTrue(bytes.length > 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private PosterConfig buildPosterConfig(Map<String, PosterConfig.PosterElement> elements) {
|
|
|
|
|
PosterConfig posterConfig = new PosterConfig();
|
|
|
|
|
posterConfig.setDefaultTemplate("default");
|
|
|
|
|
|
|
|
|
|
PosterConfig.PosterTemplate template = new PosterConfig.PosterTemplate();
|
|
|
|
|
template.setWidth(300);
|
|
|
|
|
template.setHeight(400);
|
|
|
|
|
template.setBackgroundColor("#ffffff");
|
|
|
|
|
template.setElements(elements);
|
|
|
|
|
|
|
|
|
|
Map<String, PosterConfig.PosterTemplate> templates = new HashMap<>();
|
|
|
|
|
templates.put("default", template);
|
|
|
|
|
templates.put("custom", template);
|
|
|
|
|
posterConfig.setTemplates(templates);
|
|
|
|
|
return posterConfig;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Map<String, PosterConfig.PosterElement> buildHtmlElements() {
|
|
|
|
|
Map<String, PosterConfig.PosterElement> elements = new HashMap<>();
|
|
|
|
|
elements.put("text", element("text", 10, 10, 200, 30, "Hello {{activityId}}"));
|
|
|
|
|
elements.put("qrcode", element("qrcode", 10, 50, 120, 120, "{{shortUrl}}"));
|
|
|
|
|
elements.put("image", element("image", 10, 200, 80, 80, "https://example.com/image.png"));
|
|
|
|
|
elements.put("button", element("button", 10, 300, 120, 40, "立即加入"));
|
|
|
|
|
return elements;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Map<String, PosterConfig.PosterElement> buildImageElements() {
|
|
|
|
|
Map<String, PosterConfig.PosterElement> elements = new HashMap<>();
|
|
|
|
|
elements.put("text", element("text", 10, 10, 200, 30, "Poster {{activityId}}"));
|
|
|
|
|
elements.put("qrcode", element("qrcode", 10, 60, 120, 120, "{{shortUrl}}"));
|
|
|
|
|
elements.put("rect", element("rect", 10, 200, 80, 40, ""));
|
|
|
|
|
return elements;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private PosterConfig.PosterElement element(String type, int x, int y, int width, int height, String content) {
|
|
|
|
|
PosterConfig.PosterElement element = new PosterConfig.PosterElement();
|
|
|
|
|
element.setType(type);
|
|
|
|
|
element.setX(x);
|
|
|
|
|
element.setY(y);
|
|
|
|
|
element.setWidth(width);
|
|
|
|
|
element.setHeight(height);
|
|
|
|
|
element.setContent(content);
|
2026-03-03 10:30:19 +08:00
|
|
|
element.setColor("#000000");
|
|
|
|
|
element.setFontSize("16px");
|
|
|
|
|
element.setFontFamily("Arial");
|
|
|
|
|
element.setTextAlign("left");
|
2026-03-02 13:31:54 +08:00
|
|
|
return element;
|
|
|
|
|
}
|
2026-03-03 10:30:19 +08:00
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void renderPosterHtml_shouldUseDefaultTemplate_whenTemplateNotFound() {
|
|
|
|
|
ShortLinkService shortLinkService = Mockito.mock(ShortLinkService.class);
|
|
|
|
|
ShortLinkEntity shortLink = new ShortLinkEntity();
|
|
|
|
|
shortLink.setCode("default123");
|
|
|
|
|
when(shortLinkService.create(anyString())).thenReturn(shortLink);
|
|
|
|
|
|
|
|
|
|
PosterConfig posterConfig = buildPosterConfig(buildHtmlElements());
|
|
|
|
|
PosterRenderService service = new PosterRenderService(posterConfig, shortLinkService);
|
|
|
|
|
|
|
|
|
|
String html = service.renderPosterHtml(10L, 20L, "nonexistent");
|
|
|
|
|
|
|
|
|
|
assertTrue(html.contains("/r/default123"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void renderPoster_shouldUseDefaultTemplate_whenTemplateNotFound() {
|
|
|
|
|
ShortLinkService shortLinkService = Mockito.mock(ShortLinkService.class);
|
|
|
|
|
PosterConfig posterConfig = buildPosterConfig(buildImageElements());
|
|
|
|
|
PosterRenderService service = new PosterRenderService(posterConfig, shortLinkService);
|
|
|
|
|
|
|
|
|
|
byte[] bytes = service.renderPoster(11L, 22L, "nonexistent");
|
|
|
|
|
|
|
|
|
|
assertTrue(bytes.length > 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void renderPosterHtml_shouldHandleButtonWithBackground() {
|
|
|
|
|
ShortLinkService shortLinkService = Mockito.mock(ShortLinkService.class);
|
|
|
|
|
ShortLinkEntity shortLink = new ShortLinkEntity();
|
|
|
|
|
shortLink.setCode("btn123");
|
|
|
|
|
when(shortLinkService.create(anyString())).thenReturn(shortLink);
|
|
|
|
|
|
|
|
|
|
Map<String, PosterConfig.PosterElement> elements = new HashMap<>();
|
|
|
|
|
PosterConfig.PosterElement button = element("button", 10, 10, 120, 40, "Click");
|
|
|
|
|
button.setBackground("#ff0000");
|
|
|
|
|
button.setBorderRadius("5px");
|
|
|
|
|
elements.put("button", button);
|
|
|
|
|
|
|
|
|
|
PosterConfig posterConfig = buildPosterConfig(elements);
|
|
|
|
|
PosterRenderService service = new PosterRenderService(posterConfig, shortLinkService);
|
|
|
|
|
|
|
|
|
|
String html = service.renderPosterHtml(1L, 2L, "custom");
|
|
|
|
|
|
|
|
|
|
assertTrue(html.contains("background: #ff0000"));
|
|
|
|
|
assertTrue(html.contains("border-radius: 5px"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void renderPosterHtml_shouldHandleNullContent() {
|
|
|
|
|
ShortLinkService shortLinkService = Mockito.mock(ShortLinkService.class);
|
|
|
|
|
ShortLinkEntity shortLink = new ShortLinkEntity();
|
|
|
|
|
shortLink.setCode("null123");
|
|
|
|
|
when(shortLinkService.create(anyString())).thenReturn(shortLink);
|
|
|
|
|
|
|
|
|
|
Map<String, PosterConfig.PosterElement> elements = new HashMap<>();
|
|
|
|
|
PosterConfig.PosterElement text = element("text", 10, 10, 200, 30, null);
|
|
|
|
|
elements.put("text", text);
|
|
|
|
|
|
|
|
|
|
PosterConfig posterConfig = buildPosterConfig(elements);
|
|
|
|
|
PosterRenderService service = new PosterRenderService(posterConfig, shortLinkService);
|
|
|
|
|
|
|
|
|
|
String html = service.renderPosterHtml(1L, 2L, "custom");
|
|
|
|
|
|
|
|
|
|
assertTrue(html.contains("<!DOCTYPE html>"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void renderPoster_shouldHandleRectElement() {
|
|
|
|
|
ShortLinkService shortLinkService = Mockito.mock(ShortLinkService.class);
|
|
|
|
|
|
|
|
|
|
Map<String, PosterConfig.PosterElement> elements = new HashMap<>();
|
|
|
|
|
PosterConfig.PosterElement rect = element("rect", 10, 10, 100, 50, "");
|
|
|
|
|
rect.setBackground("#00ff00");
|
|
|
|
|
elements.put("rect", rect);
|
|
|
|
|
|
|
|
|
|
PosterConfig posterConfig = buildPosterConfig(elements);
|
|
|
|
|
PosterRenderService service = new PosterRenderService(posterConfig, shortLinkService);
|
|
|
|
|
|
|
|
|
|
byte[] bytes = service.renderPoster(1L, 2L, "custom");
|
|
|
|
|
|
|
|
|
|
assertTrue(bytes.length > 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void renderPoster_shouldHandleRectWithNullBackground() {
|
|
|
|
|
ShortLinkService shortLinkService = Mockito.mock(ShortLinkService.class);
|
|
|
|
|
|
|
|
|
|
Map<String, PosterConfig.PosterElement> elements = new HashMap<>();
|
|
|
|
|
PosterConfig.PosterElement rect = element("rect", 10, 10, 100, 50, "");
|
|
|
|
|
rect.setBackground(null);
|
|
|
|
|
elements.put("rect", rect);
|
|
|
|
|
|
|
|
|
|
PosterConfig posterConfig = buildPosterConfig(elements);
|
|
|
|
|
PosterRenderService service = new PosterRenderService(posterConfig, shortLinkService);
|
|
|
|
|
|
|
|
|
|
byte[] bytes = service.renderPoster(1L, 2L, "custom");
|
|
|
|
|
|
|
|
|
|
assertTrue(bytes.length > 0);
|
|
|
|
|
}
|
2026-03-03 10:41:58 +08:00
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void renderPoster_shouldUseBackgroundImage_whenBackgroundIsNotBlank() {
|
|
|
|
|
ShortLinkService shortLinkService = Mockito.mock(ShortLinkService.class);
|
|
|
|
|
|
|
|
|
|
PosterConfig posterConfig = new PosterConfig();
|
|
|
|
|
posterConfig.setDefaultTemplate("default");
|
|
|
|
|
posterConfig.setCdnBaseUrl("https://cdn.example.com");
|
|
|
|
|
|
|
|
|
|
PosterConfig.PosterTemplate template = new PosterConfig.PosterTemplate();
|
|
|
|
|
template.setWidth(300);
|
|
|
|
|
template.setHeight(400);
|
|
|
|
|
template.setBackgroundColor("#ffffff");
|
|
|
|
|
template.setBackground("bg.jpg");
|
|
|
|
|
template.setElements(new HashMap<>());
|
|
|
|
|
|
|
|
|
|
Map<String, PosterConfig.PosterTemplate> templates = new HashMap<>();
|
|
|
|
|
templates.put("default", template);
|
|
|
|
|
posterConfig.setTemplates(templates);
|
|
|
|
|
|
|
|
|
|
PosterRenderService service = new PosterRenderService(posterConfig, shortLinkService);
|
|
|
|
|
|
|
|
|
|
byte[] bytes = service.renderPoster(1L, 2L, "default");
|
|
|
|
|
|
|
|
|
|
assertTrue(bytes.length > 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void renderPoster_shouldFallbackToBackgroundColor_whenBackgroundImageLoadFails() {
|
|
|
|
|
ShortLinkService shortLinkService = Mockito.mock(ShortLinkService.class);
|
|
|
|
|
|
|
|
|
|
PosterConfig posterConfig = new PosterConfig();
|
|
|
|
|
posterConfig.setDefaultTemplate("default");
|
|
|
|
|
posterConfig.setCdnBaseUrl("https://invalid-cdn.example.com");
|
|
|
|
|
|
|
|
|
|
PosterConfig.PosterTemplate template = new PosterConfig.PosterTemplate();
|
|
|
|
|
template.setWidth(300);
|
|
|
|
|
template.setHeight(400);
|
|
|
|
|
template.setBackgroundColor("#ff0000");
|
|
|
|
|
template.setBackground("invalid.jpg");
|
|
|
|
|
template.setElements(new HashMap<>());
|
|
|
|
|
|
|
|
|
|
Map<String, PosterConfig.PosterTemplate> templates = new HashMap<>();
|
|
|
|
|
templates.put("default", template);
|
|
|
|
|
posterConfig.setTemplates(templates);
|
|
|
|
|
|
|
|
|
|
PosterRenderService service = new PosterRenderService(posterConfig, shortLinkService);
|
|
|
|
|
|
|
|
|
|
byte[] bytes = service.renderPoster(1L, 2L, "default");
|
|
|
|
|
|
|
|
|
|
assertTrue(bytes.length > 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void renderPoster_shouldUseBackgroundColor_whenBackgroundIsBlank() {
|
|
|
|
|
ShortLinkService shortLinkService = Mockito.mock(ShortLinkService.class);
|
|
|
|
|
|
|
|
|
|
PosterConfig posterConfig = new PosterConfig();
|
|
|
|
|
posterConfig.setDefaultTemplate("default");
|
|
|
|
|
|
|
|
|
|
PosterConfig.PosterTemplate template = new PosterConfig.PosterTemplate();
|
|
|
|
|
template.setWidth(300);
|
|
|
|
|
template.setHeight(400);
|
|
|
|
|
template.setBackgroundColor("#00ff00");
|
|
|
|
|
template.setBackground(" ");
|
|
|
|
|
template.setElements(new HashMap<>());
|
|
|
|
|
|
|
|
|
|
Map<String, PosterConfig.PosterTemplate> templates = new HashMap<>();
|
|
|
|
|
templates.put("default", template);
|
|
|
|
|
posterConfig.setTemplates(templates);
|
|
|
|
|
|
|
|
|
|
PosterRenderService service = new PosterRenderService(posterConfig, shortLinkService);
|
|
|
|
|
|
|
|
|
|
byte[] bytes = service.renderPoster(1L, 2L, "default");
|
|
|
|
|
|
|
|
|
|
assertTrue(bytes.length > 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void renderPosterHtml_shouldIncludeBackgroundImage_whenBackgroundIsNotNull() {
|
|
|
|
|
ShortLinkService shortLinkService = Mockito.mock(ShortLinkService.class);
|
|
|
|
|
ShortLinkEntity shortLink = new ShortLinkEntity();
|
|
|
|
|
shortLink.setCode("bg123");
|
|
|
|
|
when(shortLinkService.create(anyString())).thenReturn(shortLink);
|
|
|
|
|
|
|
|
|
|
PosterConfig posterConfig = new PosterConfig();
|
|
|
|
|
posterConfig.setDefaultTemplate("default");
|
|
|
|
|
|
|
|
|
|
PosterConfig.PosterTemplate template = new PosterConfig.PosterTemplate();
|
|
|
|
|
template.setWidth(300);
|
|
|
|
|
template.setHeight(400);
|
|
|
|
|
template.setBackgroundColor("#ffffff");
|
|
|
|
|
template.setBackground("https://example.com/bg.jpg");
|
|
|
|
|
template.setElements(new HashMap<>());
|
|
|
|
|
|
|
|
|
|
Map<String, PosterConfig.PosterTemplate> templates = new HashMap<>();
|
|
|
|
|
templates.put("default", template);
|
|
|
|
|
posterConfig.setTemplates(templates);
|
|
|
|
|
|
|
|
|
|
PosterRenderService service = new PosterRenderService(posterConfig, shortLinkService);
|
|
|
|
|
|
|
|
|
|
String html = service.renderPosterHtml(1L, 2L, "default");
|
|
|
|
|
|
|
|
|
|
assertTrue(html.contains("background-image: url('https://example.com/bg.jpg')"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void renderPosterHtml_shouldHandleUrlEncodingException() {
|
|
|
|
|
ShortLinkService shortLinkService = Mockito.mock(ShortLinkService.class);
|
|
|
|
|
ShortLinkEntity shortLink = new ShortLinkEntity();
|
|
|
|
|
shortLink.setCode("encode123");
|
|
|
|
|
when(shortLinkService.create(anyString())).thenReturn(shortLink);
|
|
|
|
|
|
|
|
|
|
Map<String, PosterConfig.PosterElement> elements = new HashMap<>();
|
|
|
|
|
elements.put("qrcode", element("qrcode", 10, 10, 120, 120, "{{shortUrl}}"));
|
|
|
|
|
|
|
|
|
|
PosterConfig posterConfig = buildPosterConfig(elements);
|
|
|
|
|
PosterRenderService service = new PosterRenderService(posterConfig, shortLinkService);
|
|
|
|
|
|
|
|
|
|
String html = service.renderPosterHtml(1L, 2L, "custom");
|
|
|
|
|
|
|
|
|
|
assertTrue(html.contains("data="));
|
|
|
|
|
}
|
2026-03-03 17:24:15 +08:00
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void renderPosterHtml_shouldEscapeHtmlInTitle() {
|
|
|
|
|
ShortLinkService shortLinkService = Mockito.mock(ShortLinkService.class);
|
|
|
|
|
ShortLinkEntity shortLink = new ShortLinkEntity();
|
|
|
|
|
shortLink.setCode("escape123");
|
|
|
|
|
when(shortLinkService.create(anyString())).thenReturn(shortLink);
|
|
|
|
|
|
|
|
|
|
PosterConfig posterConfig = buildPosterConfig(new HashMap<>());
|
|
|
|
|
PosterRenderService service = new PosterRenderService(posterConfig, shortLinkService);
|
|
|
|
|
|
|
|
|
|
// 创建一个带有HTML特殊字符的Activity
|
|
|
|
|
String html = service.renderPosterHtml(1L, 2L, "custom");
|
|
|
|
|
|
|
|
|
|
// escapeHtml应该被调用,虽然activity是null,但方法会处理
|
|
|
|
|
assertTrue(html.contains("<title>"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void renderPoster_shouldHandleInvalidFontSize() {
|
|
|
|
|
ShortLinkService shortLinkService = Mockito.mock(ShortLinkService.class);
|
|
|
|
|
|
|
|
|
|
Map<String, PosterConfig.PosterElement> elements = new HashMap<>();
|
|
|
|
|
PosterConfig.PosterElement text = element("text", 10, 10, 200, 30, "Test");
|
|
|
|
|
text.setFontSize("invalid-size"); // 无效的字体大小
|
|
|
|
|
elements.put("text", text);
|
|
|
|
|
|
|
|
|
|
PosterConfig posterConfig = buildPosterConfig(elements);
|
|
|
|
|
PosterRenderService service = new PosterRenderService(posterConfig, shortLinkService);
|
|
|
|
|
|
|
|
|
|
// 应该使用默认字体大小16,不抛异常
|
|
|
|
|
byte[] bytes = service.renderPoster(1L, 2L, "custom");
|
|
|
|
|
assertTrue(bytes.length > 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void renderPoster_shouldUseDefaultTemplate_whenTemplateNameIsNull() {
|
|
|
|
|
ShortLinkService shortLinkService = Mockito.mock(ShortLinkService.class);
|
|
|
|
|
PosterConfig posterConfig = buildPosterConfig(buildImageElements());
|
|
|
|
|
PosterRenderService service = new PosterRenderService(posterConfig, shortLinkService);
|
|
|
|
|
|
|
|
|
|
// 传入null模板名,应该使用默认模板
|
|
|
|
|
byte[] bytes = service.renderPoster(1L, 2L, null);
|
|
|
|
|
assertTrue(bytes.length > 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void renderPosterHtml_shouldUseDefaultTemplate_whenTemplateNameIsNull() {
|
|
|
|
|
ShortLinkService shortLinkService = Mockito.mock(ShortLinkService.class);
|
|
|
|
|
ShortLinkEntity shortLink = new ShortLinkEntity();
|
|
|
|
|
shortLink.setCode("null123");
|
|
|
|
|
when(shortLinkService.create(anyString())).thenReturn(shortLink);
|
|
|
|
|
|
|
|
|
|
PosterConfig posterConfig = buildPosterConfig(buildHtmlElements());
|
|
|
|
|
PosterRenderService service = new PosterRenderService(posterConfig, shortLinkService);
|
|
|
|
|
|
|
|
|
|
// 传入null模板名,应该使用默认模板
|
|
|
|
|
String html = service.renderPosterHtml(1L, 2L, null);
|
|
|
|
|
assertTrue(html.contains("/r/null123"));
|
|
|
|
|
}
|
2026-03-02 13:31:54 +08:00
|
|
|
}
|