From 09cd2c12e2d9ace0124eaa81551d382c003241c2 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Tue, 15 Dec 2015 15:25:19 +1300 Subject: [PATCH] Add tests for @Draftable from example application --- .../avaje/tests/draftable/DocLinkTest.java | 272 ++++++++++++++++++ .../tests/draftable/LinkQueryPublishTest.java | 48 ++++ .../tests/draftable/OrganisationTest.java | 116 ++++++++ .../tests/model/draftable/BaseDomain.java | 58 ++++ .../com/avaje/tests/model/draftable/Doc.java | 39 +++ .../avaje/tests/model/draftable/Document.java | 75 +++++ .../tests/model/draftable/DocumentMedia.java | 45 +++ .../com/avaje/tests/model/draftable/Link.java | 126 ++++++++ .../tests/model/draftable/Organisation.java | 21 ++ 9 files changed, 800 insertions(+) create mode 100644 src/test/java/com/avaje/tests/draftable/DocLinkTest.java create mode 100644 src/test/java/com/avaje/tests/draftable/LinkQueryPublishTest.java create mode 100644 src/test/java/com/avaje/tests/draftable/OrganisationTest.java create mode 100644 src/test/java/com/avaje/tests/model/draftable/BaseDomain.java create mode 100644 src/test/java/com/avaje/tests/model/draftable/Doc.java create mode 100644 src/test/java/com/avaje/tests/model/draftable/Document.java create mode 100644 src/test/java/com/avaje/tests/model/draftable/DocumentMedia.java create mode 100644 src/test/java/com/avaje/tests/model/draftable/Link.java create mode 100644 src/test/java/com/avaje/tests/model/draftable/Organisation.java diff --git a/src/test/java/com/avaje/tests/draftable/DocLinkTest.java b/src/test/java/com/avaje/tests/draftable/DocLinkTest.java new file mode 100644 index 000000000..4e11ffbbc --- /dev/null +++ b/src/test/java/com/avaje/tests/draftable/DocLinkTest.java @@ -0,0 +1,272 @@ +package com.avaje.tests.draftable; + +import com.avaje.ebean.Ebean; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.Query; +import com.avaje.tests.model.draftable.Doc; +import com.avaje.tests.model.draftable.Link; +import org.assertj.core.api.StrictAssertions; +import org.junit.Test; + +import javax.persistence.PersistenceException; +import java.sql.Timestamp; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertTrue; + +public class DocLinkTest { + + @Test + public void testUpdate_whenNotPublished() { + + Link link1 = new Link("update"); + StrictAssertions.assertThat(link1.isDraft()).isFalse(); + + link1.save(); + StrictAssertions.assertThat(link1.isDraft()).isTrue(); + + // perform stateless update + Link linkUpdate = new Link(); + linkUpdate.setId(link1.getId()); + linkUpdate.setComment("stateless update"); + linkUpdate.setDraft(true); + linkUpdate.update(); + + // invoke lazy loading on the updated bean + // automatically set asDraft() on lazy loading query + linkUpdate.getLocation(); + + Ebean.deletePermanent(linkUpdate); + } + + @Test + public void testDelete_whenNotPublished() { + + Link link1 = new Link("Ld1"); + StrictAssertions.assertThat(link1.isDraft()).isFalse(); + + link1.save(); + StrictAssertions.assertThat(link1.isDraft()).isTrue(); + + link1.setComment("some change"); + link1.save(); + + Ebean.delete(link1); + } + + + @Test + public void testDeletePermanent_whenPublished2() { + + Link link1 = new Link("Ld2"); + link1.save(); + Ebean.getDefaultServer().publish(Link.class, link1.getId()); + + Link link = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique(); + Ebean.deletePermanent(link); + } + + @Test + public void testDeleteLivePermanent_throwsException() { + + Link link1 = new Link("Ld2"); + link1.save(); + + Link live = Ebean.getDefaultServer().publish(Link.class, link1.getId()); + + try { + Ebean.deletePermanent(live); + assertTrue("never get here",false); + + } catch (PersistenceException e) { + // assert nice message when trying to delete live bean + assertThat(e.getMessage().contains("Explicit Delete is not allowed on a 'live' bean - only draft beans")); + } + } + + @Test + public void testDelete_whenPublished() { + + Link link1 = new Link("Ld2"); + link1.save(); + EbeanServer server = Ebean.getDefaultServer(); + server.publish(Link.class, link1.getId()); + + link1 = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique(); + StrictAssertions.assertThat(link1.isDraft()).isTrue(); + + // this is a soft delete (no automatic publish here, only updates draft) + link1.delete(); + + Link live = Ebean.find(Link.class).setId(link1.getId()).findUnique(); + assertThat(live).isNotNull(); + StrictAssertions.assertThat(live.isDraft()).isFalse(); + StrictAssertions.assertThat(live.isDeleted()).isFalse(); // soft delete state not published yet + + // this is a permanent delete (effectively has automatic publish) + server.deletePermanent(link1); + + live = Ebean.find(Link.class).setId(link1.getId()).findUnique(); + assertThat(live).isNull(); + } + + @Test + public void testUpdateLive_throwsException() { + + Link link1 = new Link("forUpdateLive"); + link1.save(); + + Link live = Ebean.getDefaultServer().publish(Link.class, link1.getId()); + + live.setComment("foo"); + // Expect a nice + try { + live.save(); + assertTrue("Never get here",false); + + } catch (PersistenceException e) { + // we want to assert the message is nice and meaningful (and not a optimistic locking exception etc) + assertThat(e.getMessage()).contains("Save or update is not allowed on a 'live' bean - only draft beans"); + } + } + + @Test + public void testDirtyState() { + + Timestamp when = new Timestamp(System.currentTimeMillis()); + String comment = "Really interesting"; + + Link link1 = new Link("Ls1"); + link1.setComment(comment); + link1.setWhenPublish(when); + link1.save(); + + Link draft1 = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique(); + StrictAssertions.assertThat(draft1.isDirty()).isTrue(); + + EbeanServer server = Ebean.getDefaultServer(); + + Link linkLive = server.publish(Link.class, link1.getId(), null); + StrictAssertions.assertThat(linkLive.getComment()).isEqualTo(comment); + StrictAssertions.assertThat(linkLive.getWhenPublish()).isEqualTo(when); + + Link draft1b = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique(); + StrictAssertions.assertThat(draft1b.isDirty()).isFalse(); + StrictAssertions.assertThat(draft1b.getComment()).isNull(); + StrictAssertions.assertThat(draft1b.getWhenPublish()).isNull(); + + } + + @Test + public void testSave() { + + Link link1 = new Link("LinkOne"); + link1.save(); + + Link link2 = new Link("LinkTwo"); + link2.save(); + + Link link3 = new Link("LinkThree"); + link3.save(); + + EbeanServer server = Ebean.getDefaultServer(); + server.publish(Link.class, link1.getId(), null); + server.publish(Link.class, link2.getId(), null); + server.publish(Link.class, link3.getId(), null); + + Doc doc1 = new Doc("DocOne"); + doc1.getLinks().add(link1); + doc1.getLinks().add(link2); + doc1.save(); + + Doc draftDoc1 = server.find(Doc.class) + .setId(doc1.getId()) + .asDraft() + .findUnique(); + + assertThat(draftDoc1.getLinks()).hasSize(2); + + Doc liveDoc1 = server.publish(Doc.class, doc1.getId(), null); + + assertThat(liveDoc1.getLinks()).hasSize(2); + assertThat(liveDoc1.getLinks()).extracting("id").contains(link1.getId(), link2.getId()); + + + draftDoc1.getLinks().remove(0); + draftDoc1.getLinks().add(link3); + + draftDoc1.save(); + + // publish with insert and delete of Links M2M relationship + Doc liveDoc2 = server.publish(Doc.class, doc1.getId(), null); + assertThat(liveDoc2.getLinks()).hasSize(2); + assertThat(liveDoc2.getLinks()).extracting("id").contains(link2.getId(), link3.getId()); + + // delete the draft and live beans (with associated children) + draftDoc1.delete(); + } + + + @Test + public void testDraftRestore() { + + Link link1 = new Link("Ldr1"); + link1.setLocation("firstLocation"); + link1.save(); + + EbeanServer server = Ebean.getDefaultServer(); + + Link live = server.publish(Link.class, link1.getId(), null); + StrictAssertions.assertThat(live.isDraft()).isFalse(); + + Link draftLink = Ebean.find(Link.class) + .setId(link1.getId()) + .asDraft() + .findUnique(); + + draftLink.setLocation("secondLocation"); + draftLink.save(); + + server.draftRestore(Link.class, link1.getId(), null); + + draftLink = Ebean.find(Link.class) + .setId(link1.getId()) + .asDraft() + .findUnique(); + + StrictAssertions.assertThat(draftLink.getLocation()).isEqualTo("firstLocation"); + + } + + @Test + public void testDraftRestoreViaQuery() { + + Link link1 = new Link("Ldr1"); + link1.setLocation("firstLocation"); + link1.setComment("Banana"); + link1.save(); + + EbeanServer server = Ebean.getDefaultServer(); + + server.publish(Link.class, link1.getId(), null); + + Link draftLink = Ebean.find(Link.class) + .setId(link1.getId()) + .asDraft() + .findUnique(); + + draftLink.setLocation("secondLocation"); + draftLink.setComment("A good change"); + draftLink.save(); + + Query query = server.find(Link.class).where().eq("id", link1.getId()).query(); + List links = server.draftRestore(query); + + assertThat(links).hasSize(1); + StrictAssertions.assertThat(links.get(0).getLocation()).isEqualTo("firstLocation"); + StrictAssertions.assertThat(links.get(0).isDirty()).isEqualTo(false); + StrictAssertions.assertThat(links.get(0).getComment()).isNull(); + + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/tests/draftable/LinkQueryPublishTest.java b/src/test/java/com/avaje/tests/draftable/LinkQueryPublishTest.java new file mode 100644 index 000000000..79b6e557b --- /dev/null +++ b/src/test/java/com/avaje/tests/draftable/LinkQueryPublishTest.java @@ -0,0 +1,48 @@ +package com.avaje.tests.draftable; + +import com.avaje.ebean.Ebean; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.Query; +import com.avaje.tests.model.draftable.Link; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LinkQueryPublishTest { + + @Test + public void testPublishViaQuery() { + + Link link1 = new Link("L1"); + link1.save(); + + Link link2 = new Link("L2"); + link2.save(); + + Link link3 = new Link("L3"); + link3.save(); + + EbeanServer server = Ebean.getDefaultServer(); + + List ids = new ArrayList(); + ids.add(link1.getId()); + ids.add(link2.getId()); + ids.add(link3.getId()); + + Query pubQuery = server.find(Link.class) + .where().idIn(ids) + .order().asc("id"); + + + List pubList = server.publish(pubQuery); + + assertThat(pubList).hasSize(3); + assertThat(pubList).extracting("id").contains(link1.getId(), link2.getId(), link3.getId()); + + } + + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/tests/draftable/OrganisationTest.java b/src/test/java/com/avaje/tests/draftable/OrganisationTest.java new file mode 100644 index 000000000..a02d8c1b5 --- /dev/null +++ b/src/test/java/com/avaje/tests/draftable/OrganisationTest.java @@ -0,0 +1,116 @@ +package com.avaje.tests.draftable; + +import com.avaje.ebean.Ebean; +import com.avaje.ebean.EbeanServer; +import com.avaje.tests.model.draftable.Document; +import com.avaje.tests.model.draftable.DocumentMedia; +import com.avaje.tests.model.draftable.Organisation; +import org.assertj.core.api.StrictAssertions; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +public class OrganisationTest { + + @Test + public void testSave() { + + Organisation org = new Organisation("OrgOne"); + org.save(); + + assertNotNull(org.getId()); + + Document doc = new Document(); + doc.setTitle("NewTitle"); + doc.setOrganisation(org); + doc.setBody("Hello"); + + doc.save(); + + doc.setBody("Change content"); + doc.save(); + + + EbeanServer server = Ebean.getDefaultServer(); + + Document draftDoc = server.find(Document.class) + .asDraft() + .setId(doc.getId()) + .findUnique(); + + assertNotNull(draftDoc); + + Document liveDoc = server.find(Document.class) + .setId(doc.getId()) + .findUnique(); + assertNull(liveDoc); + + + server.publish(Document.class, doc.getId(), null); + + doc.setTitle("Mod1"); + doc.save(); + + server.publish(Document.class, doc.getId(), null); + + } + + + + @Test + public void testSaveWithCascade() { + + Organisation org = new Organisation("Org2"); + org.save(); + + Document doc = new Document(); + doc.setTitle("Title1"); + doc.setOrganisation(org); + doc.setBody("Body1"); + + doc.getMedia().add(createMedia("media1")); + doc.getMedia().add(createMedia("media2")); + doc.save(); + + EbeanServer server = Ebean.getDefaultServer(); + + + server.publish(Document.class, doc.getId(), null); + + Document fetchDoc = Ebean.find(Document.class).setId(doc.getId()).asDraft().findUnique(); + List media = fetchDoc.getMedia(); + + assertThat(media.size()).isEqualTo(2); + +// // delete one of the 'child' @DraftElement rows ... +// SqlUpdate sqlUpdate = Ebean.createSqlUpdate("delete from document_media_draft where id = ?"); +// sqlUpdate.setParameter(1, doc.getMedia().get(0).getId()); +// sqlUpdate.execute(); + + doc.getMedia().get(1).setDescription("mod"); + doc.getMedia().add(createMedia("media3")); + doc.getMedia().remove(0); + doc.setBody("Body2"); + doc.save(); + + // publish will perform an insert, update and delete on child DocumentMedia + // during the publish below with media1 being deleted + Document liveBean = server.publish(Document.class, doc.getId(), null); + StrictAssertions.assertThat(liveBean.getBody()).isEqualTo("Body2"); + StrictAssertions.assertThat(liveBean.getMedia().size()).isEqualTo(2); + assertThat(liveBean.getMedia()).extracting("name").contains("media2","media3"); + + } + + @NotNull + private DocumentMedia createMedia(String name) { + DocumentMedia media = new DocumentMedia(); + media.setName(name); + return media; + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/tests/model/draftable/BaseDomain.java b/src/test/java/com/avaje/tests/model/draftable/BaseDomain.java new file mode 100644 index 000000000..722b97261 --- /dev/null +++ b/src/test/java/com/avaje/tests/model/draftable/BaseDomain.java @@ -0,0 +1,58 @@ +package com.avaje.tests.model.draftable; + +import com.avaje.ebean.Model; +import com.avaje.ebean.annotation.WhenCreated; +import com.avaje.ebean.annotation.WhenModified; + +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import javax.persistence.Version; +import java.sql.Timestamp; + +@MappedSuperclass +public class BaseDomain extends Model { + + @Id + Long id; + + @Version + Long version; + + @WhenCreated + Timestamp whenCreated; + + @WhenModified + Timestamp whenModified; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } + + public Timestamp getWhenCreated() { + return whenCreated; + } + + public void setWhenCreated(Timestamp whenCreated) { + this.whenCreated = whenCreated; + } + + public Timestamp getWhenModified() { + return whenModified; + } + + public void setWhenModified(Timestamp whenModified) { + this.whenModified = whenModified; + } +} diff --git a/src/test/java/com/avaje/tests/model/draftable/Doc.java b/src/test/java/com/avaje/tests/model/draftable/Doc.java new file mode 100644 index 000000000..59e80fc94 --- /dev/null +++ b/src/test/java/com/avaje/tests/model/draftable/Doc.java @@ -0,0 +1,39 @@ +package com.avaje.tests.model.draftable; + +import com.avaje.ebean.annotation.Draftable; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.ManyToMany; +import java.util.List; + + +@Draftable +@Entity +public class Doc extends BaseDomain { + + String name; + + @ManyToMany(cascade = CascadeType.ALL) + List links; + + public Doc(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getLinks() { + return links; + } + + public void setLinks(List links) { + this.links = links; + } +} diff --git a/src/test/java/com/avaje/tests/model/draftable/Document.java b/src/test/java/com/avaje/tests/model/draftable/Document.java new file mode 100644 index 000000000..ea7d184ff --- /dev/null +++ b/src/test/java/com/avaje/tests/model/draftable/Document.java @@ -0,0 +1,75 @@ +package com.avaje.tests.model.draftable; + +import com.avaje.ebean.annotation.DraftOnly; +import com.avaje.ebean.annotation.Draftable; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import java.sql.Timestamp; +import java.util.List; + + +@Draftable +@Entity +public class Document extends BaseDomain { + + @Column(unique = true) + String title; + + String body; + + @DraftOnly + Timestamp whenPublish; + + @ManyToOne + Organisation organisation; + + /** + * Relationship to draftable elements. + */ + //@PrivateOwned + @OneToMany(mappedBy = "document")//, cascade = CascadeType.ALL) + List media; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public Organisation getOrganisation() { + return organisation; + } + + public void setOrganisation(Organisation organisation) { + this.organisation = organisation; + } + + public List getMedia() { + return media; + } + + public void setMedia(List media) { + this.media = media; + } + + public Timestamp getWhenPublish() { + return whenPublish; + } + + public void setWhenPublish(Timestamp whenPublish) { + this.whenPublish = whenPublish; + } +} diff --git a/src/test/java/com/avaje/tests/model/draftable/DocumentMedia.java b/src/test/java/com/avaje/tests/model/draftable/DocumentMedia.java new file mode 100644 index 000000000..19fdcaa34 --- /dev/null +++ b/src/test/java/com/avaje/tests/model/draftable/DocumentMedia.java @@ -0,0 +1,45 @@ +package com.avaje.tests.model.draftable; + +import com.avaje.ebean.annotation.DraftableElement; + +import javax.persistence.Entity; +import javax.persistence.ManyToOne; + +/** + * 'Owned' by @Draftable root. + */ +@DraftableElement +@Entity +public class DocumentMedia extends BaseDomain { + + @ManyToOne + Document document; + + String name; + + String description; + + public Document getDocument() { + return document; + } + + public void setDocument(Document document) { + this.document = document; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/src/test/java/com/avaje/tests/model/draftable/Link.java b/src/test/java/com/avaje/tests/model/draftable/Link.java new file mode 100644 index 000000000..db7c77915 --- /dev/null +++ b/src/test/java/com/avaje/tests/model/draftable/Link.java @@ -0,0 +1,126 @@ +package com.avaje.tests.model.draftable; + +import com.avaje.ebean.annotation.Draft; +import com.avaje.ebean.annotation.DraftDirty; +import com.avaje.ebean.annotation.DraftReset; +import com.avaje.ebean.annotation.Draftable; +import com.avaje.ebean.annotation.History; +import com.avaje.ebean.annotation.SoftDelete; + +import javax.persistence.Entity; +import javax.persistence.ManyToMany; +import java.sql.Timestamp; +import java.util.List; + + +//@History +@Draftable +@Entity +public class Link extends BaseDomain { + + @SoftDelete + boolean deleted; + + String name; + + String location; + + /** + * Draft reset to null on publish. + */ + @DraftReset + Timestamp whenPublish; + + /** + * Draft reset to null on publish. + */ + @DraftReset + String comment; + + /** + * Indicates if the instance is a 'draft' or 'live' bean. + */ + @Draft + boolean draft; + + /** + * Indicates if the draft has modifications that have not been published + * to 'live'. This is automatically set when a draft is saved. + */ + @DraftDirty + boolean dirty; + + @ManyToMany(mappedBy = "links") + List docs; + + public Link(String name) { + this.name = name; + } + + public Link() { + } + + public boolean isDraft() { + return draft; + } + + public void setDraft(boolean draft) { + this.draft = draft; + } + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public List getDocs() { + return docs; + } + + public void setDocs(List docs) { + this.docs = docs; + } + + public boolean isDirty() { + return dirty; + } + + public void setDirty(boolean dirty) { + this.dirty = dirty; + } + + public Timestamp getWhenPublish() { + return whenPublish; + } + + public void setWhenPublish(Timestamp whenPublish) { + this.whenPublish = whenPublish; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } +} diff --git a/src/test/java/com/avaje/tests/model/draftable/Organisation.java b/src/test/java/com/avaje/tests/model/draftable/Organisation.java new file mode 100644 index 000000000..2f1d8a979 --- /dev/null +++ b/src/test/java/com/avaje/tests/model/draftable/Organisation.java @@ -0,0 +1,21 @@ +package com.avaje.tests.model.draftable; + +import javax.persistence.Entity; + +@Entity +public class Organisation extends BaseDomain { + + String name; + + public Organisation(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +}