Remove route when single pages become unpublished (#4318)

#### What type of PR is this?

/kind bug
/area core
/milestone 2.8.x

#### What this PR does / why we need it:

- Remove route when single pages become unpublished
- Add some unit tests against the change.

#### Which issue(s) this PR fixes:

Fixes https://github.com/halo-dev/halo/issues/4309

#### Special notes for your reviewer:

1. Create a single page with slug name `about.html` and publish it.
2. Create a static file `about.html` into static folder`${halo.work-dir}/static/`.
3. Try to request <http://localhost:8090/about.html> and check the result.
4. Unpublish the single page and then do the step 3 again.

#### Does this PR introduce a user-facing change?

```release-note
修复页面取消发布后访问仍然出现页面未找到错误。
```
pull/4322/head
John Niang 2023-07-28 11:05:09 +08:00 committed by GitHub
parent 576a3763fd
commit 54925efdd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 128 additions and 11 deletions

View File

@ -101,15 +101,21 @@ public class SinglePageRoute
public Result reconcile(Request request) {
client.fetch(SinglePage.class, request.name())
.ifPresent(page -> {
if (ExtensionOperator.isDeleted(page)
|| BooleanUtils.isTrue(page.getSpec().getDeleted())) {
quickRouteMap.remove(NameSlugPair.from(page));
var nameSlugPair = NameSlugPair.from(page);
if (ExtensionOperator.isDeleted(page)) {
quickRouteMap.remove(nameSlugPair);
return;
}
// put new one
quickRouteMap.entrySet()
.removeIf(entry -> entry.getKey().name().equals(request.name()));
quickRouteMap.put(NameSlugPair.from(page), handlerFunction(request.name()));
if (BooleanUtils.isTrue(page.getSpec().getDeleted())) {
quickRouteMap.remove(nameSlugPair);
} else {
// put new one
if (page.isPublished()) {
quickRouteMap.put(nameSlugPair, handlerFunction(request.name()));
} else {
quickRouteMap.remove(nameSlugPair);
}
}
});
return new Result(false, null);
}

View File

@ -2,16 +2,24 @@ package run.halo.app.theme.router;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.net.URI;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@ -31,8 +39,10 @@ import org.springframework.web.util.UriUtils;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import run.halo.app.core.extension.content.SinglePage;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.GroupVersionKind;
import run.halo.app.extension.Metadata;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.finders.SinglePageFinder;
import run.halo.app.theme.finders.vo.SinglePageVo;
@ -48,16 +58,19 @@ import run.halo.app.theme.router.SinglePageRoute.NameSlugPair;
class SinglePageRouteTest {
@Mock
private ViewNameResolver viewNameResolver;
ViewNameResolver viewNameResolver;
@Mock
private SinglePageFinder singlePageFinder;
SinglePageFinder singlePageFinder;
@Mock
protected ViewResolver viewResolver;
ViewResolver viewResolver;
@Mock
ExtensionClient client;
@InjectMocks
private SinglePageRoute singlePageRoute;
SinglePageRoute singlePageRoute;
@Test
void handlerFunction() {
@ -130,4 +143,102 @@ class SinglePageRouteTest {
.verifyComplete();
}
@Nested
class SinglePageReconcilerTest {
@Test
void shouldRemoveRouteIfSinglePageUnpublished() {
var name = "fake-single-page";
var page = newSinglePage(name, false);
when(client.fetch(SinglePage.class, name)).thenReturn(
Optional.of(page));
var routeMap = Mockito.<Map<NameSlugPair, HandlerFunction<ServerResponse>>>mock(
invocation -> new HashMap<NameSlugPair, HandlerFunction<ServerResponse>>());
singlePageRoute.setQuickRouteMap(routeMap);
var result = singlePageRoute.reconcile(new Reconciler.Request(name));
assertNotNull(result);
assertFalse(result.reEnqueue());
verify(client).fetch(SinglePage.class, name);
verify(routeMap).remove(NameSlugPair.from(page));
}
@Test
void shouldAddRouteIfSinglePagePublished() {
var name = "fake-single-page";
var page = newSinglePage(name, true);
when(client.fetch(SinglePage.class, name)).thenReturn(
Optional.of(page));
var routeMap = Mockito.<Map<NameSlugPair, HandlerFunction<ServerResponse>>>mock(
invocation -> new HashMap<NameSlugPair, HandlerFunction<ServerResponse>>());
singlePageRoute.setQuickRouteMap(routeMap);
var result = singlePageRoute.reconcile(new Reconciler.Request(name));
assertNotNull(result);
assertFalse(result.reEnqueue());
verify(client).fetch(SinglePage.class, name);
verify(routeMap).put(eq(NameSlugPair.from(page)), any());
}
@Test
void shouldRemoveRouteIfSinglePageDeleted() {
var name = "fake-single-page";
var page = newDeletedSinglePage(name);
when(client.fetch(SinglePage.class, name)).thenReturn(
Optional.of(page));
var routeMap = Mockito.<Map<NameSlugPair, HandlerFunction<ServerResponse>>>mock(
invocation -> new HashMap<NameSlugPair, HandlerFunction<ServerResponse>>());
singlePageRoute.setQuickRouteMap(routeMap);
var result = singlePageRoute.reconcile(new Reconciler.Request(name));
assertNotNull(result);
assertFalse(result.reEnqueue());
verify(client).fetch(SinglePage.class, name);
verify(routeMap).remove(NameSlugPair.from(page));
}
@Test
void shouldRemoveRouteIfSinglePageRecycled() {
var name = "fake-single-page";
var page = newRecycledSinglePage(name);
when(client.fetch(SinglePage.class, name)).thenReturn(
Optional.of(page));
var routeMap = Mockito.<Map<NameSlugPair, HandlerFunction<ServerResponse>>>mock(
invocation -> new HashMap<NameSlugPair, HandlerFunction<ServerResponse>>());
singlePageRoute.setQuickRouteMap(routeMap);
var result = singlePageRoute.reconcile(new Reconciler.Request(name));
assertNotNull(result);
assertFalse(result.reEnqueue());
verify(client).fetch(SinglePage.class, name);
verify(routeMap).remove(NameSlugPair.from(page));
}
SinglePage newSinglePage(String name, boolean published) {
var metadata = new Metadata();
metadata.setName(name);
var page = new SinglePage();
page.setMetadata(metadata);
var spec = new SinglePage.SinglePageSpec();
spec.setSlug("/fake-slug");
page.setSpec(spec);
var status = new SinglePage.SinglePageStatus();
page.setStatus(status);
SinglePage.changePublishedState(page, published);
return page;
}
SinglePage newDeletedSinglePage(String name) {
var page = newSinglePage(name, true);
page.getMetadata().setDeletionTimestamp(Instant.now());
return page;
}
SinglePage newRecycledSinglePage(String name) {
var page = newSinglePage(name, true);
page.getSpec().setDeleted(true);
return page;
}
}
}