Skip to content

Commit c016227

Browse files
authored
fix(categories): default CategoryForm.active to true on omitted JSON (#35501) (#35502)
## Summary `CategoryForm` declared `private boolean active;` (a Java primitive that defaults to `false`). Because `CategoriesResource.fillAndSave()` runs `BeanUtils.copyProperties(category, categoryForm)` on POST, an omitted `active` field on the request silently overwrote the `Category` model's `true` default — every category created from the new Angular categories portlet (which doesn't send `active`) was created inactive. Defaulting both the form field and the inner `Builder` field to `true` makes omitted JSON behave like the model: omitted ⇒ active, explicit `true`/`false` ⇒ honored. <img width="1602" height="874" alt="CleanShot 2026-04-29 at 16 08 38@2x" src="https://github.com/user-attachments/assets/c116526b-6cf8-4198-b86a-38000402ca50" /> Closes #35501 ## Acceptance Criteria - [x] **AC1** — POST without `active` ⇒ category active (verified by `deserialize_omittedActive_defaultsToTrue`) - [x] **AC2** — POST with `"active": false` ⇒ category inactive (verified by `deserialize_explicitActiveFalse_isPreserved`) - [x] **AC3** — POST with `"active": true` ⇒ category active (verified by `deserialize_explicitActiveTrue_isPreserved`) - [x] **AC4/AC5** — PUT semantics unchanged: `fillAndSave` (PUT overload) seeds the update from the existing entity and only re-applies `categoryName`, `key`, `keywords` from the form, so the form's `active` was never read on PUT before this fix and is still not read after — existing values are preserved when omitted. - [x] **AC6** — New Angular categories portlet creates active categories by default (it omits `active`, which now defaults to `true` server-side) - [x] **AC7** — No retroactive change: only the default for new POSTs changes; existing rows are untouched. - [x] **AC8** — Unit test added: `CategoryFormTest` covers all three create scenarios + Builder default. ## Test plan - [x] `./mvnw test -pl :dotcms-core -Dtest=CategoryFormTest` — 4/4 passing locally - [ ] Manual: create a category through the new Angular categories portlet and verify it appears as active - [ ] CI: full test suite ## Changed files - `dotCMS/src/main/java/com/dotcms/rest/api/v1/categories/CategoryForm.java` — default `active = true` on class field and Builder field - `dotCMS/src/test/java/com/dotcms/rest/api/v1/categories/CategoryFormTest.java` — new unit test
1 parent 58cb228 commit c016227

2 files changed

Lines changed: 69 additions & 1 deletion

File tree

dotCMS/src/main/java/com/dotcms/rest/api/v1/categories/CategoryForm.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public static final class Builder {
9696
@JsonProperty
9797
private String categoryName;
9898
@JsonProperty
99-
private boolean active;
99+
private boolean active = true;
100100
@JsonProperty
101101
private int sortOrder;
102102
@JsonProperty
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.dotcms.rest.api.v1.categories;
2+
3+
import static org.junit.Assert.assertFalse;
4+
import static org.junit.Assert.assertTrue;
5+
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import java.io.IOException;
8+
import org.junit.Test;
9+
10+
/**
11+
* Unit tests for {@link CategoryForm} Jackson deserialization, focused on the
12+
* {@code active} field default behavior (issue #35501).
13+
*/
14+
public class CategoryFormTest {
15+
16+
private final ObjectMapper mapper = new ObjectMapper();
17+
18+
/**
19+
* AC1: A create payload that omits {@code active} must default to {@code true},
20+
* matching the {@code Category} model default and avoiding silently inactive categories.
21+
*/
22+
@Test
23+
public void deserialize_omittedActive_defaultsToTrue() throws IOException {
24+
final String json = "{\"categoryName\":\"adds\",\"categoryVelocityVarName\":\"adds\","
25+
+ "\"key\":\"asd\",\"keywords\":\"asd\"}";
26+
27+
final CategoryForm form = mapper.readValue(json, CategoryForm.class);
28+
29+
assertTrue("active must default to true when omitted from JSON", form.isActive());
30+
}
31+
32+
/**
33+
* AC2: An explicit {@code "active": false} in the payload must be honored.
34+
*/
35+
@Test
36+
public void deserialize_explicitActiveFalse_isPreserved() throws IOException {
37+
final String json = "{\"categoryName\":\"x\",\"active\":false}";
38+
39+
final CategoryForm form = mapper.readValue(json, CategoryForm.class);
40+
41+
assertFalse("explicit active=false must be preserved", form.isActive());
42+
}
43+
44+
/**
45+
* AC3: An explicit {@code "active": true} in the payload must be honored.
46+
*/
47+
@Test
48+
public void deserialize_explicitActiveTrue_isPreserved() throws IOException {
49+
final String json = "{\"categoryName\":\"x\",\"active\":true}";
50+
51+
final CategoryForm form = mapper.readValue(json, CategoryForm.class);
52+
53+
assertTrue("explicit active=true must be preserved", form.isActive());
54+
}
55+
56+
/**
57+
* Builder default mirrors the deserialization default — guards against the
58+
* field being reset to the primitive default in either side of the form.
59+
*/
60+
@Test
61+
public void builder_defaultActive_isTrue() {
62+
final CategoryForm form = new CategoryForm.Builder()
63+
.setCategoryName("x")
64+
.build();
65+
66+
assertTrue("Builder default for active must be true", form.isActive());
67+
}
68+
}

0 commit comments

Comments
 (0)