diff --git a/CHANGELOG.md b/CHANGELOG.md
index b33941d..e508bb8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,31 @@
# Changelog
All notable changes to this project will be documented in this file.
+## [1.99.1]
+
+### Added
+
+- Add support for form features:
+ - `AccessControl` model - Represents permissions for view/edit access to forms
+ - `Form` model - Complete form definition including field definitions
+ - `FormField` abstract model – for all form field types
+ - `FormState` enum - Enumeration for form publishing states (NEW, PUBLISHED, UNPUBLISHED, OLD)
+ - `ChoiceFormField` model - Checkbox field with multiple selection support
+ - `DateFormField` model - Date input field with min/max validation
+ - `NumberFormField` model - Numeric field with range and decimal places
+ - `RadioFormField` model - Radio button field for single selection
+ - `StringFormField` model - Short text input field (max 255 characters)
+ - `TextFormField` model - Long text field supporting HTML content
+ - `TimeFormField` model - Time input field storing millisecond values
+
+### Changed
+
+- Enhanced `FormInfo` model with additional properties:
+ - Added `formState` property for tracking publishing status
+ - Added `accessControl` property for permission management
+ - Added `tags` property for form categorization
+ - Added `iconId` property for form visual identification
+
## [1.99.0]
- switch from rspace-os-parent to rspace-parent as parent pom
@@ -17,10 +42,10 @@ All notable changes to this project will be documented in this file.
### Added
- Introduced support for Inventory features:
- - `Barcode` model
- - `TagInfo` model
- - `RecordInfo` model
- - `SharedWith` model
+ - `Barcode` model
+ - `TagInfo` model
+ - `RecordInfo` model
+ - `SharedWith` model
- Added the ability to represent sharing mode, barcodes, and tags for Inventory.
## [1.97.0]
diff --git a/pom.xml b/pom.xml
index 3929352..628e493 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0
rspace-client-java-model
- 1.99.0
+ 1.99.1
rspace-client-java-model
com.github.rspace-os
diff --git a/src/main/java/com/researchspace/api/clientmodel/AccessControl.java b/src/main/java/com/researchspace/api/clientmodel/AccessControl.java
new file mode 100644
index 0000000..28b3d16
--- /dev/null
+++ b/src/main/java/com/researchspace/api/clientmodel/AccessControl.java
@@ -0,0 +1,21 @@
+package com.researchspace.api.clientmodel;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * Information about the permissions to view or edit a form.
+ *
+ * @author rspace
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AccessControl {
+ private String ownerPermissionType;
+ private String groupPermissionType;
+ private String worldPermissionType;
+}
\ No newline at end of file
diff --git a/src/main/java/com/researchspace/api/clientmodel/ChoiceFormField.java b/src/main/java/com/researchspace/api/clientmodel/ChoiceFormField.java
new file mode 100644
index 0000000..a6d2ac3
--- /dev/null
+++ b/src/main/java/com/researchspace/api/clientmodel/ChoiceFormField.java
@@ -0,0 +1,41 @@
+package com.researchspace.api.clientmodel;
+
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import javax.validation.constraints.Size;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A Choice form field defines a list of checkboxes, some of which can be selected by default.
+ * @author rspace
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@SuperBuilder
+@JsonPropertyOrder(value = {"id", "globalId", "name", "type", "lastModified", "index",
+ "multipleChoice", "options", "defaultOptions"})
+public class ChoiceFormField extends FormField {
+
+ private boolean multipleChoice;
+ @Size(min = 1, message = "Please provide at least one option")
+ @Builder.Default
+ private List options = new ArrayList<>();
+ @Builder.Default
+ private List defaultOptions = new ArrayList<>();
+
+ public ChoiceFormField(String name, boolean multipleChoice, List options, List defaultOptions) {
+ super();
+ setName(name);
+ setType("Choice");
+ this.multipleChoice = multipleChoice;
+ this.options = options != null ? options : new ArrayList<>();
+ this.defaultOptions = defaultOptions != null ? defaultOptions : new ArrayList<>();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/researchspace/api/clientmodel/DateFormField.java b/src/main/java/com/researchspace/api/clientmodel/DateFormField.java
new file mode 100644
index 0000000..0ea57ae
--- /dev/null
+++ b/src/main/java/com/researchspace/api/clientmodel/DateFormField.java
@@ -0,0 +1,42 @@
+package com.researchspace.api.clientmodel;
+
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.researchspace.api.jackson.ISO8601DateSerialiser;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.Date;
+
+/**
+ * A Date form field - defines a DateField.
+ * @author rspace
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@SuperBuilder
+@JsonPropertyOrder(value = {"id", "globalId", "name", "type", "lastModified", "index",
+ "defaultValue", "min", "max"})
+public class DateFormField extends FormField {
+
+
+ @JsonSerialize(using = ISO8601DateSerialiser.class)
+ private Date defaultValue;
+ @JsonSerialize(using = ISO8601DateSerialiser.class)
+ private Date min;
+
+ @JsonSerialize(using = ISO8601DateSerialiser.class)
+ private Date max;
+
+ public DateFormField(String name, Date defaultValue, Date min, Date max) {
+ super();
+ setName(name);
+ setType("Date");
+ this.defaultValue = defaultValue != null ? new Date(defaultValue.getTime()) : null;
+ this.min = min != null ? new Date(min.getTime()) : null;
+ this.max = max != null ? new Date(max.getTime()) : null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/researchspace/api/clientmodel/Form.java b/src/main/java/com/researchspace/api/clientmodel/Form.java
new file mode 100644
index 0000000..a5592af
--- /dev/null
+++ b/src/main/java/com/researchspace/api/clientmodel/Form.java
@@ -0,0 +1,42 @@
+package com.researchspace.api.clientmodel;
+
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A Form. A complete form definition including field definitions.
+ * @author rspace
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@SuperBuilder
+@JsonPropertyOrder(value = {
+ "id", "globalId", "stableId", "version", "name", "tags",
+ "formState", "accessControl", "fields", "_links"
+})
+public class Form extends FormInfo {
+
+ @Builder.Default
+ private List fields = new ArrayList<>();
+
+ /**
+ * Adds a field to this form
+ * @param field the FormField to add
+ * @return this Form for method chaining
+ */
+ public Form addField(FormField field) {
+ if (this.fields == null) {
+ this.fields = new ArrayList<>();
+ }
+ this.fields.add(field);
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/researchspace/api/clientmodel/FormField.java b/src/main/java/com/researchspace/api/clientmodel/FormField.java
new file mode 100644
index 0000000..56f7eef
--- /dev/null
+++ b/src/main/java/com/researchspace/api/clientmodel/FormField.java
@@ -0,0 +1,49 @@
+package com.researchspace.api.clientmodel;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * A FormField definition. This is an abstract type for all form field types.
+ * The properties listed here are common to all types.
+ *
+ * @author rspace
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+@SuperBuilder
+@JsonTypeInfo(
+ use = Id.NAME,
+ include = JsonTypeInfo.As.PROPERTY,
+ property = "type",
+ visible = true
+)
+@JsonSubTypes({
+ @Type(value = ChoiceFormField.class, name = "Choice"),
+ @Type(value = TextFormField.class, name = "Text"),
+ @Type(value = StringFormField.class, name = "String"),
+ @Type(value = NumberFormField.class, name = "Number"),
+ @Type(value = RadioFormField.class, name = "Radio"),
+ @Type(value = DateFormField.class, name = "Date"),
+ @Type(value = TimeFormField.class, name = "Time"),
+})
+@JsonPropertyOrder(value = {"id", "globalId", "name", "type", "lastModified", "index"})
+public abstract class FormField extends IdentifiableNameable {
+
+ @JsonProperty("lastModified")
+ private String lastModifiedMillis;
+ private Integer index;
+ @JsonProperty("type")
+ private String type;
+}
\ No newline at end of file
diff --git a/src/main/java/com/researchspace/api/clientmodel/FormInfo.java b/src/main/java/com/researchspace/api/clientmodel/FormInfo.java
index 7f59f14..08209fa 100644
--- a/src/main/java/com/researchspace/api/clientmodel/FormInfo.java
+++ b/src/main/java/com/researchspace/api/clientmodel/FormInfo.java
@@ -24,9 +24,12 @@
package com.researchspace.api.clientmodel;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
/**
* The Form on which the document structure is based.
@@ -34,9 +37,19 @@
@Data
@EqualsAndHashCode(callSuper=true)
@NoArgsConstructor
-public class FormInfo extends IdentifiableNameable {
+@AllArgsConstructor
+@SuperBuilder
+@JsonPropertyOrder(value = {
+ "id", "globalId", "stableId", "version", "name", "tags",
+ "formState", "accessControl", "iconId", "_links"
+})
+public class FormInfo extends IdentifiableNameable {
- private String stableId = null;
- private Integer version = null;
+ private String stableId;
+ private Integer version;
+ private FormState formState;
+ private AccessControl accessControl;
+ private String tags;
+ private Long iconId;
}
diff --git a/src/main/java/com/researchspace/api/clientmodel/FormState.java b/src/main/java/com/researchspace/api/clientmodel/FormState.java
new file mode 100644
index 0000000..33c61f5
--- /dev/null
+++ b/src/main/java/com/researchspace/api/clientmodel/FormState.java
@@ -0,0 +1,14 @@
+package com.researchspace.api.clientmodel;
+
+/**
+ * Enumeration representing the publishing state of a Form.
+ *
+ * @author rspace
+ */
+public enum FormState {
+
+ NEW,
+ PUBLISHED,
+ UNPUBLISHED,
+ OLD
+}
\ No newline at end of file
diff --git a/src/main/java/com/researchspace/api/clientmodel/NumberFormField.java b/src/main/java/com/researchspace/api/clientmodel/NumberFormField.java
new file mode 100644
index 0000000..d88bb9d
--- /dev/null
+++ b/src/main/java/com/researchspace/api/clientmodel/NumberFormField.java
@@ -0,0 +1,37 @@
+package com.researchspace.api.clientmodel;
+
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import javax.validation.constraints.Min;
+
+/**
+ * A Number form field - for numeric input with constraints.
+ * @author rspace
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@SuperBuilder
+@JsonPropertyOrder(value = {"id", "globalId", "name", "type", "lastModified", "index",
+ "min", "max", "decimalPlaces", "defaultValue"})
+public class NumberFormField extends FormField {
+ private Double min;
+ private Double max;
+ @Min(0)
+ private Byte decimalPlaces;
+ private Double defaultValue;
+
+ public NumberFormField(String name, Double defaultValue, Double min, Double max, Byte decimalPlaces) {
+ super();
+ setName(name);
+ setType("Number");
+ this.defaultValue = defaultValue;
+ this.min = min;
+ this.max = max;
+ this.decimalPlaces = decimalPlaces;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/researchspace/api/clientmodel/RadioFormField.java b/src/main/java/com/researchspace/api/clientmodel/RadioFormField.java
new file mode 100644
index 0000000..764fa60
--- /dev/null
+++ b/src/main/java/com/researchspace/api/clientmodel/RadioFormField.java
@@ -0,0 +1,38 @@
+package com.researchspace.api.clientmodel;
+
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import javax.validation.constraints.Size;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A Radio form field defines a list of radio buttons, one of which can be selected by default.
+ * @author rspace
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@SuperBuilder
+@JsonPropertyOrder(value = {"id", "globalId", "name", "type", "lastModified", "index",
+ "options", "defaultOption"})
+public class RadioFormField extends FormField {
+
+ @Size(min = 1, message = "Please provide at least one option")
+ @Builder.Default
+ private List options = new ArrayList<>();
+ private String defaultOption;
+
+ public RadioFormField(String name, List options, String defaultOption) {
+ super();
+ setName(name);
+ setType("Radio");
+ this.options = options != null ? options : new ArrayList<>();
+ this.defaultOption = defaultOption;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/researchspace/api/clientmodel/StringFormField.java b/src/main/java/com/researchspace/api/clientmodel/StringFormField.java
new file mode 100644
index 0000000..a66c5e2
--- /dev/null
+++ b/src/main/java/com/researchspace/api/clientmodel/StringFormField.java
@@ -0,0 +1,28 @@
+package com.researchspace.api.clientmodel;
+
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * A String form field - for short text input (max 255 characters).
+ * @author rspace
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@SuperBuilder
+@JsonPropertyOrder(value = {"id", "globalId", "name", "type", "lastModified", "index", "defaultValue"})
+public class StringFormField extends FormField {
+
+ private String defaultValue;
+
+ public StringFormField(String name, String defaultValue) {
+ super();
+ setName(name);
+ setType("String");
+ this.defaultValue = defaultValue;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/researchspace/api/clientmodel/TextFormField.java b/src/main/java/com/researchspace/api/clientmodel/TextFormField.java
new file mode 100644
index 0000000..a9cdb61
--- /dev/null
+++ b/src/main/java/com/researchspace/api/clientmodel/TextFormField.java
@@ -0,0 +1,28 @@
+package com.researchspace.api.clientmodel;
+
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * A Text form field - for longer text input that can include HTML.
+ * @author rspace
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@SuperBuilder
+@JsonPropertyOrder(value = {"id", "globalId", "name", "type", "lastModified", "index", "defaultValue"})
+public class TextFormField extends FormField {
+
+ private String defaultValue;
+
+ public TextFormField(String name, String defaultValue) {
+ super();
+ setName(name);
+ setType("Text");
+ this.defaultValue = defaultValue;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/researchspace/api/clientmodel/TimeFormField.java b/src/main/java/com/researchspace/api/clientmodel/TimeFormField.java
new file mode 100644
index 0000000..a88657c
--- /dev/null
+++ b/src/main/java/com/researchspace/api/clientmodel/TimeFormField.java
@@ -0,0 +1,28 @@
+package com.researchspace.api.clientmodel;
+
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * A Time form field - for time input values stored as milliseconds.
+ * @author rspace
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@SuperBuilder
+@JsonPropertyOrder(value = {"id", "globalId", "name", "type", "lastModified", "index", "defaultValue"})
+public class TimeFormField extends FormField {
+
+ private Long defaultValue;
+
+ public TimeFormField(String name, Long defaultValue) {
+ super();
+ setName(name);
+ setType("Time");
+ this.defaultValue = defaultValue;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/researchspace/api/clientmodel/AccessControlTest.java b/src/test/java/com/researchspace/api/clientmodel/AccessControlTest.java
new file mode 100644
index 0000000..8d979c7
--- /dev/null
+++ b/src/test/java/com/researchspace/api/clientmodel/AccessControlTest.java
@@ -0,0 +1,63 @@
+package com.researchspace.api.clientmodel;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class AccessControlTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ void setUp() {
+ objectMapper = new ObjectMapper();
+ objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+ }
+
+ @Test
+ void testAccessControlBuilder() {
+ AccessControl accessControl = AccessControl.builder()
+ .ownerPermissionType("WRITE")
+ .groupPermissionType("READ")
+ .worldPermissionType("NONE")
+ .build();
+
+ assertNotNull(accessControl);
+ assertEquals("WRITE", accessControl.getOwnerPermissionType());
+ assertEquals("READ", accessControl.getGroupPermissionType());
+ assertEquals("NONE", accessControl.getWorldPermissionType());
+ }
+
+ @Test
+ void testAccessControlConstructors() {
+
+ AccessControl ac1 = new AccessControl();
+ assertNotNull(ac1);
+ assertNull(ac1.getOwnerPermissionType());
+
+ AccessControl ac2 = new AccessControl("WRITE", "READ", "NONE");
+ assertEquals("WRITE", ac2.getOwnerPermissionType());
+ assertEquals("READ", ac2.getGroupPermissionType());
+ assertEquals("NONE", ac2.getWorldPermissionType());
+ }
+
+ @Test
+ void testAccessControlSerialization() throws JsonProcessingException {
+ AccessControl accessControl = new AccessControl("WRITE", "READ", "NONE");
+
+ String json = objectMapper.writeValueAsString(accessControl);
+ assertNotNull(json);
+ assertTrue(json.contains("\"ownerPermissionType\":\"WRITE\""));
+ assertTrue(json.contains("\"groupPermissionType\":\"READ\""));
+ assertTrue(json.contains("\"worldPermissionType\":\"NONE\""));
+
+ AccessControl deserialized = objectMapper.readValue(json, AccessControl.class);
+ assertEquals("WRITE", deserialized.getOwnerPermissionType());
+ assertEquals("READ", deserialized.getGroupPermissionType());
+ assertEquals("NONE", deserialized.getWorldPermissionType());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/researchspace/api/clientmodel/FormFieldTest.java b/src/test/java/com/researchspace/api/clientmodel/FormFieldTest.java
new file mode 100644
index 0000000..0e974f8
--- /dev/null
+++ b/src/test/java/com/researchspace/api/clientmodel/FormFieldTest.java
@@ -0,0 +1,135 @@
+package com.researchspace.api.clientmodel;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Date;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class FormFieldTest {
+
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ void setUp() {
+ objectMapper = new ObjectMapper();
+ objectMapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
+ objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
+ objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+ }
+
+ @Test
+ void testStringFormFieldSerialization() throws JsonProcessingException {
+ StringFormField field = new StringFormField("Test String Field", "test default");
+ field.setId(1L);
+ field.setIndex(0);
+
+ String json = objectMapper.writeValueAsString(field);
+ assertNotNull(json);
+ assertTrue(json.contains("\"type\":\"String\""));
+ assertTrue(json.contains("\"defaultValue\":\"test default\""));
+
+ FormField deserialized = objectMapper.readValue(json, FormField.class);
+ assertInstanceOf(StringFormField.class, deserialized);
+ StringFormField stringField = (StringFormField) deserialized;
+ assertEquals("Test String Field", stringField.getName());
+ assertEquals("test default", stringField.getDefaultValue());
+ }
+
+ @Test
+ void testNumberFormFieldSerialization() throws JsonProcessingException {
+ NumberFormField field = new NumberFormField("Test Number Field", 50.5, 0.0, 100.0, (byte) 2);
+ field.setId(2L);
+ field.setIndex(1);
+
+ String json = objectMapper.writeValueAsString(field);
+ assertNotNull(json);
+ assertTrue(json.contains("\"type\":\"Number\""));
+ assertTrue(json.contains("\"min\":0.0"));
+ assertTrue(json.contains("\"max\":100.0"));
+
+ FormField deserialized = objectMapper.readValue(json, FormField.class);
+ assertInstanceOf(NumberFormField.class, deserialized);
+ NumberFormField numberField = (NumberFormField) deserialized;
+ assertEquals("Test Number Field", numberField.getName());
+ assertEquals(Double.valueOf(50.5), numberField.getDefaultValue());
+ assertEquals(Double.valueOf(0.0), numberField.getMin());
+ assertEquals(Double.valueOf(100.0), numberField.getMax());
+ }
+
+ @Test
+ void testChoiceFormFieldSerialization() throws JsonProcessingException {
+ ChoiceFormField field = new ChoiceFormField("Test Choice Field", true,
+ Arrays.asList("Option A", "Option B", "Option C"),
+ Arrays.asList("Option A", "Option B"));
+ field.setId(3L);
+ field.setIndex(2);
+
+ String json = objectMapper.writeValueAsString(field);
+ assertNotNull(json);
+ assertTrue(json.contains("\"type\":\"Choice\""));
+ assertTrue(json.contains("\"multipleChoice\":true"));
+
+ FormField deserialized = objectMapper.readValue(json, FormField.class);
+ assertInstanceOf(ChoiceFormField.class, deserialized);
+ ChoiceFormField choiceField = (ChoiceFormField) deserialized;
+ assertEquals("Test Choice Field", choiceField.getName());
+ assertTrue(choiceField.isMultipleChoice());
+ assertEquals(3, choiceField.getOptions().size());
+ assertEquals(2, choiceField.getDefaultOptions().size());
+ }
+
+ @Test
+ void testDateFormFieldSerialization() throws JsonProcessingException {
+ Date defaultDate = new Date(1640995200000L); // 2022-01-01
+ Date minDate = new Date(1609459200000L); // 2021-01-01
+ Date maxDate = new Date(1672531200000L); // 2023-01-01
+
+ DateFormField field = new DateFormField("Test Date Field", defaultDate, minDate, maxDate);
+ field.setId(4L);
+ field.setIndex(3);
+
+ String json = objectMapper.writeValueAsString(field);
+ assertNotNull(json);
+ assertTrue(json.contains("\"type\":\"Date\""));
+
+ FormField deserialized = objectMapper.readValue(json, FormField.class);
+ assertInstanceOf(DateFormField.class, deserialized);
+ DateFormField dateField = (DateFormField) deserialized;
+ assertEquals("Test Date Field", dateField.getName());
+ assertNotNull(dateField.getDefaultValue());
+ assertNotNull(dateField.getMin());
+ assertNotNull(dateField.getMax());
+ }
+
+ @Test
+ void testTimeFormFieldSerialization() throws JsonProcessingException {
+ long defaultTime = 3600000L;
+ TimeFormField field = new TimeFormField("Test Time Field", defaultTime);
+ field.setId(7L);
+ field.setIndex(6);
+ String json = objectMapper.writeValueAsString(field);
+ assertNotNull(json);
+ assertTrue(json.contains("\"type\":\"Time\""));
+ assertTrue(json.contains("\"defaultValue\":" + defaultTime));
+ FormField deserialized = objectMapper.readValue(json, FormField.class);
+ assertInstanceOf(TimeFormField.class, deserialized);
+ TimeFormField timeField = (TimeFormField) deserialized;
+ assertEquals("Test Time Field", timeField.getName());
+ assertEquals(defaultTime, timeField.getDefaultValue());
+ }
+
+ @Test
+ void testConstructors() {
+ StringFormField stringField = new StringFormField("String Test", "default");
+ assertEquals("String Test", stringField.getName());
+ assertEquals("String", stringField.getType());
+ assertEquals("default", stringField.getDefaultValue());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/researchspace/api/clientmodel/FormInfoTest.java b/src/test/java/com/researchspace/api/clientmodel/FormInfoTest.java
new file mode 100644
index 0000000..6ed0b50
--- /dev/null
+++ b/src/test/java/com/researchspace/api/clientmodel/FormInfoTest.java
@@ -0,0 +1,83 @@
+package com.researchspace.api.clientmodel;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class FormInfoTest extends AbstractModelTest {
+
+ @Test
+ void testFormInfoFromFormSearchResult() throws IOException {
+
+ File formSearchResultJson = new File("src/test/resources/FormSearchResult.json");
+ FormSearchResult formSearchResult = readFileToClass(formSearchResultJson, FormSearchResult.class);
+
+ assertNotNull(formSearchResult.getForms());
+ assertFalse(formSearchResult.getForms().isEmpty());
+
+ FormInfo firstForm = formSearchResult.getForms().get(0);
+ assertNotNull(firstForm);
+ assertEquals(Long.valueOf(32785), firstForm.getId());
+ assertEquals("FM32785", firstForm.getGlobalId());
+ assertEquals("formName", firstForm.getName());
+ assertEquals("a,b,c", firstForm.getTags());
+ assertEquals(FormState.PUBLISHED, firstForm.getFormState());
+ assertNotNull(firstForm.getAccessControl());
+ assertEquals("WRITE", firstForm.getAccessControl().getOwnerPermissionType());
+ assertEquals("NONE", firstForm.getAccessControl().getGroupPermissionType());
+ assertEquals("NONE", firstForm.getAccessControl().getWorldPermissionType());
+ assertEquals(Long.valueOf(-1), firstForm.getIconId());
+ }
+
+ @Test
+ void testFormInfoBuilder() {
+ AccessControl accessControl = AccessControl.builder()
+ .ownerPermissionType("WRITE")
+ .groupPermissionType("READ")
+ .worldPermissionType("NONE")
+ .build();
+
+ FormInfo formInfo = FormInfo.builder()
+ .id(123L)
+ .globalId("FM123")
+ .name("Test Form")
+ .stableId("stable123")
+ .version(1)
+ .tags("tag1,tag2")
+ .formState(FormState.NEW)
+ .accessControl(accessControl)
+ .iconId(456L)
+ .build();
+
+ assertNotNull(formInfo);
+ assertEquals(Long.valueOf(123), formInfo.getId());
+ assertEquals("FM123", formInfo.getGlobalId());
+ assertEquals("Test Form", formInfo.getName());
+ assertEquals("stable123", formInfo.getStableId());
+ assertEquals(Integer.valueOf(1), formInfo.getVersion());
+ assertEquals("tag1,tag2", formInfo.getTags());
+ assertEquals(FormState.NEW, formInfo.getFormState());
+ assertEquals(accessControl, formInfo.getAccessControl());
+ assertEquals(Long.valueOf(456), formInfo.getIconId());
+ }
+
+ @Test
+ void testFormInfoConstructors() {
+
+ FormInfo formInfo1 = new FormInfo();
+ assertNotNull(formInfo1);
+ assertNull(formInfo1.getId());
+
+ AccessControl ac = new AccessControl("WRITE", "READ", "NONE");
+ FormInfo formInfo2 = new FormInfo("stable", 1, FormState.PUBLISHED, ac, "tags", 123L);
+ assertEquals("stable", formInfo2.getStableId());
+ assertEquals(Integer.valueOf(1), formInfo2.getVersion());
+ assertEquals(FormState.PUBLISHED, formInfo2.getFormState());
+ assertEquals(ac, formInfo2.getAccessControl());
+ assertEquals("tags", formInfo2.getTags());
+ assertEquals(Long.valueOf(123), formInfo2.getIconId());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/researchspace/api/clientmodel/FormTest.java b/src/test/java/com/researchspace/api/clientmodel/FormTest.java
new file mode 100644
index 0000000..f4db6ea
--- /dev/null
+++ b/src/test/java/com/researchspace/api/clientmodel/FormTest.java
@@ -0,0 +1,85 @@
+package com.researchspace.api.clientmodel;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class FormTest extends AbstractModelTest {
+
+ File formJson = new File("src/test/resources/Form.json");
+
+ @Test
+ void testFormDeserialization() throws IOException {
+ Form form = readFileToClass(formJson, Form.class);
+
+ assertNotNull(form);
+ assertEquals(Long.valueOf(123), form.getId());
+ assertEquals("FM123", form.getGlobalId());
+ assertEquals("Sample Form", form.getName());
+ assertEquals("tag1,tag2", form.getTags());
+ assertEquals(FormState.PUBLISHED, form.getFormState());
+ assertNotNull(form.getAccessControl());
+ assertEquals("WRITE", form.getAccessControl().getOwnerPermissionType());
+
+ assertNotNull(form.getFields());
+ assertEquals(3, form.getFields().size());
+
+ assertInstanceOf(StringFormField.class, form.getFields().get(0));
+ assertInstanceOf(NumberFormField.class, form.getFields().get(1));
+ assertInstanceOf(ChoiceFormField.class, form.getFields().get(2));
+
+ StringFormField stringField = (StringFormField) form.getFields().get(0);
+ assertEquals("Sample Text Field", stringField.getName());
+ assertEquals("String", stringField.getType());
+ assertEquals("default value", stringField.getDefaultValue());
+
+ NumberFormField numberField = (NumberFormField) form.getFields().get(1);
+ assertEquals("Sample Number Field", numberField.getName());
+ assertEquals("Number", numberField.getType());
+ assertEquals(Double.valueOf(10.5), numberField.getDefaultValue());
+ assertEquals(Double.valueOf(0.0), numberField.getMin());
+ assertEquals(Double.valueOf(100.0), numberField.getMax());
+
+ ChoiceFormField choiceField = (ChoiceFormField) form.getFields().get(2);
+ assertEquals("Sample Choice Field", choiceField.getName());
+ assertEquals("Choice", choiceField.getType());
+ assertEquals(3, choiceField.getOptions().size());
+ assertEquals(2, choiceField.getDefaultOptions().size());
+ }
+
+ @Test
+ void testFormBuilder() {
+ Form form = Form.builder()
+ .id(456L)
+ .name("Test Form")
+ .tags("test,builder")
+ .formState(FormState.NEW)
+ .build();
+
+ assertNotNull(form);
+ assertEquals(Long.valueOf(456), form.getId());
+ assertEquals("Test Form", form.getName());
+ assertEquals("test,builder", form.getTags());
+ assertEquals(FormState.NEW, form.getFormState());
+ assertNotNull(form.getFields()); // Should be initialized as empty list
+ assertTrue(form.getFields().isEmpty());
+ }
+
+ @Test
+ void testAddField() {
+ Form form = Form.builder().build();
+ StringFormField field = new StringFormField("test", "default");
+
+ form.addField(field);
+
+ assertEquals(1, form.getFields().size());
+ assertEquals(field, form.getFields().get(0));
+
+ Form result = form.addField(new TextFormField("text", "text default"));
+ assertSame(form, result);
+ assertEquals(2, form.getFields().size());
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/Form.json b/src/test/resources/Form.json
new file mode 100644
index 0000000..72420ac
--- /dev/null
+++ b/src/test/resources/Form.json
@@ -0,0 +1,55 @@
+{
+ "id": 123,
+ "globalId": "FM123",
+ "stableId": "stable123",
+ "version": 1,
+ "name": "Sample Form",
+ "tags": "tag1,tag2",
+ "formState": "PUBLISHED",
+ "accessControl": {
+ "ownerPermissionType": "WRITE",
+ "groupPermissionType": "READ",
+ "worldPermissionType": "NONE"
+ },
+ "iconId": 456,
+ "fields": [
+ {
+ "id": 1,
+ "globalId": "FF1",
+ "name": "Sample Text Field",
+ "type": "String",
+ "lastModified": "2022-01-01T00:00:00Z",
+ "index": 0,
+ "defaultValue": "default value"
+ },
+ {
+ "id": 2,
+ "globalId": "FF2",
+ "name": "Sample Number Field",
+ "type": "Number",
+ "lastModified": "2022-01-01T00:00:00Z",
+ "index": 1,
+ "min": 0.0,
+ "max": 100.0,
+ "decimalPlaces": 1,
+ "defaultValue": 10.5
+ },
+ {
+ "id": 3,
+ "globalId": "FF3",
+ "name": "Sample Choice Field",
+ "type": "Choice",
+ "lastModified": "2022-01-01T00:00:00Z",
+ "index": 2,
+ "multipleChoice": true,
+ "options": ["Option 1", "Option 2", "Option 3"],
+ "defaultOptions": ["Option 1", "Option 2"]
+ }
+ ],
+ "_links": [
+ {
+ "link": "http://localhost:8080/api/v1/forms/123",
+ "rel": "self"
+ }
+ ]
+}
\ No newline at end of file