Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch wiki Excluding Merge-Ins
This is equivalent to a diff from ad120f4256 to c6f863dd80
2024-08-04
| ||
21:11 | Merged back links check-in: c743e4b553 user: scstarkey tags: main, trunk | |
21:11 | Back links Closed-Leaf check-in: c6f863dd80 user: scstarkey tags: wiki | |
2024-08-02
| ||
14:40 | Brought fixes back in from main check-in: 993637cf98 user: scstarkey tags: wiki | |
14:31 | Switched to basic from standard check-in: ad120f4256 user: scstarkey tags: main, trunk | |
14:03 | Switching to NIOFSDirectory to see if it'll start up in podman container check-in: a2f0bd53de user: scstarkey tags: main, trunk | |
Changes to src/main/java/app/coffeetime/SearchInterface.java.
1 2 3 4 5 6 7 8 9 10 | package app.coffeetime; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.core.WhitespaceAnalyzer; import org.apache.lucene.analysis.miscellaneous.PerFieldAnalyzerWrapper; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.TextField; import org.apache.lucene.index.*; | | < < < | | > > > > > > > > > > > > > > > > > < < > | < > | < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | package app.coffeetime; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.core.WhitespaceAnalyzer; import org.apache.lucene.analysis.miscellaneous.PerFieldAnalyzerWrapper; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.TextField; import org.apache.lucene.index.*; import org.apache.lucene.search.*; import org.apache.lucene.store.Directory; import org.apache.lucene.store.NIOFSDirectory; import org.apache.lucene.util.QueryBuilder; import org.jetbrains.annotations.NotNull; import org.pcollections.*; import org.slf4j.Logger; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; import static app.coffeetime.util.Logging.logSupplier; public abstract class SearchInterface { private static final Supplier<Logger> LOG = logSupplier(SearchInterface.class); public record SearchField(String name, Object value, boolean stored, boolean analyzed) { public SearchField(final String name, final Object value) { this(name, value, false, false); } } public record SearchResult(PMap<String, SearchField> searchFields) { } public record SearchResults(long totalRecords, PVector<SearchResult> searchResults) { } public static final SearchResults EMPTY_SEARCH_RESULTS = new SearchResults(0, TreePVector.empty()); public static final SearchInterface SEARCH_NOOP = new SearchInterface() { @Override public void save(final PVector<SearchField> fields) { } @Override public void delete(final SearchField field) { } @Override public Optional<SearchResult> findDocument(final TreePVector<SearchField> searchFields) { return Optional.empty(); } @Override public SearchResults findDocuments(final TreePVector<SearchField> searchFields, int limit) { return EMPTY_SEARCH_RESULTS; } }; public static SearchInterface disk(final String path, final PVector<String> notAnalyzedFields) { try { final var indexPath = Path.of(path); final var indexFile = indexPath.toFile(); |
︙ | ︙ | |||
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | return new SearchInterface() { @Override public void save(final PVector<SearchField> fields) { try { final var document = new Document(); fields.forEach(field -> document.add(new Field(field.name(), String.valueOf(field.value()), field.stored() ? TextField.TYPE_STORED : TextField.TYPE_NOT_STORED))); try (final var writer = writerSupplier.get()) { writer.addDocument(document); } } catch (IOException e) { throw new IllegalStateException(e); } } @Override public void delete(final SearchField field) { try (final var writer = writerSupplier.get()) { writer.deleteDocuments(term(field)); } catch (IOException e) { throw new IllegalStateException(e); } } @Override | > | | | < | > > > > | | | | | > > > > | > > > > > | > > > > | > > > | 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 | return new SearchInterface() { @Override public void save(final PVector<SearchField> fields) { try { final var document = new Document(); fields.forEach(field -> document.add(new Field(field.name(), String.valueOf(field.value()), field.stored() ? TextField.TYPE_STORED : TextField.TYPE_NOT_STORED))); try (final var writer = writerSupplier.get()) { writer.addDocument(document); } } catch (IOException e) { throw new IllegalStateException(e); } } @Override public void delete(final SearchField field) { try (final var writer = writerSupplier.get()) { writer.deleteDocuments(term(field)); } catch (IOException e) { throw new IllegalStateException(e); } } @Override public SearchResults findDocuments(final TreePVector<SearchField> searchFields, int limit) { try { final var queryBuilder = new BooleanQuery.Builder(); searchFields.forEach(searchField -> queryBuilder.add(new BooleanClause(buildQuery(analyzer, searchField), BooleanClause.Occur.MUST))); final var finalQuery = queryBuilder.build(); try (final var reader = readerSupplier.get()) { final var searchResult = new IndexSearcher(reader).search(finalQuery, limit); if (searchResult.totalHits.value > 0) { return new SearchResults(searchResult.totalHits.value, Stream.of(searchResult.scoreDocs[0]) .map(scoreDoc -> { try { final var document = reader.storedFields().document(scoreDoc.doc); return new SearchResult(document.getFields().stream() .reduce(HashTreePMap.empty(), (m, f) -> m.plus(f.name(), new SearchField(f.name(), f.stringValue())), HashPMap::plusAll)); } catch (IOException e) { throw new IllegalStateException(e); } }).reduce(TreePVector.empty(), TreePVector::plus, TreePVector::plusAll)); } else { return EMPTY_SEARCH_RESULTS; } } } catch (IOException e) { throw new IllegalStateException(e); } } @Override public Optional<SearchResult> findDocument(final TreePVector<SearchField> searchFields) { return findDocuments(searchFields, 1).searchResults().stream().findFirst(); } @NotNull private static Query buildQuery(final Analyzer analyzer, final SearchField searchField) { if (searchField.analyzed()) { return new QueryBuilder(analyzer).createPhraseQuery(searchField.name(), String.valueOf(searchField.value())); } return new TermQuery(term(searchField)); } @NotNull private static Term term(final SearchField searchField) { return new Term(searchField.name(), String.valueOf(searchField.value())); } }; } catch (Throwable t) { LOG.get().error("Unable to initialize search engine", t); return SEARCH_NOOP; } } public abstract void save(PVector<SearchField> fields); public abstract void delete(final SearchField field); public abstract Optional<SearchResult> findDocument(final TreePVector<SearchField> searchFields); public abstract SearchResults findDocuments(final TreePVector<SearchField> searchFields, int limit); } |
Changes to src/main/java/app/coffeetime/models/WikiModel.java.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package app.coffeetime.models; import app.coffeetime.DatabaseInterface; import app.coffeetime.SearchInterface; import app.coffeetime.SearchInterface.SearchField; import org.apache.commons.io.IOUtils; import org.pcollections.PVector; import org.pcollections.TreePVector; import org.slf4j.Logger; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.PreparedStatement; import java.time.Instant; import java.util.Optional; import java.util.TimeZone; import java.util.function.Supplier; import static app.coffeetime.DatabaseInterface.*; import static app.coffeetime.util.Logging.logSupplier; import static java.util.Optional.ofNullable; public class WikiModel { private static final Supplier<Logger> LOG = logSupplier(WikiModel.class); public static final String SEARCH_FIELD_LATEST = "wikiIsLatest"; public static final String SEARCH_FIELD_PAGE_ID = "wikiPageId"; public static final String SEARCH_FIELD_TEAM_ID = "wikiTeamId"; public static final String SEARCH_FIELD_PAGE_NAME = "wikiPageName"; public static final String SEARCH_FIELD_VERSION = "wikiPageVersion"; public static final String SEARCH_FIELD_CONTENT = "wikiContent"; | > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | package app.coffeetime.models; import app.coffeetime.DatabaseInterface; import app.coffeetime.SearchInterface; import app.coffeetime.SearchInterface.SearchField; import app.coffeetime.util.TextSanitizer; import org.apache.commons.io.IOUtils; import org.jetbrains.annotations.NotNull; import org.pcollections.PVector; import org.pcollections.TreePVector; import org.slf4j.Logger; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.PreparedStatement; import java.time.Instant; import java.util.Optional; import java.util.TimeZone; import java.util.function.Supplier; import java.util.regex.Pattern; import static app.coffeetime.DatabaseInterface.*; import static app.coffeetime.util.Logging.logSupplier; import static java.lang.String.format; import static java.util.Optional.ofNullable; public class WikiModel { private static final Pattern WIKI_LINK_PATTERN = Pattern.compile("\\[\\[([^\\[]+)]]"); private static final Supplier<Logger> LOG = logSupplier(WikiModel.class); public static final String SEARCH_FIELD_LATEST = "wikiIsLatest"; public static final String SEARCH_FIELD_PAGE_ID = "wikiPageId"; public static final String SEARCH_FIELD_TEAM_ID = "wikiTeamId"; public static final String SEARCH_FIELD_PAGE_NAME = "wikiPageName"; public static final String SEARCH_FIELD_VERSION = "wikiPageVersion"; public static final String SEARCH_FIELD_CONTENT = "wikiContent"; |
︙ | ︙ | |||
73 74 75 76 77 78 79 | } }) .orElse("Welcome to your wiki!"); } }.get(); } | | > > | < < < < < < < | | | | | | > > > | > | > > > > > | | | > | > > > > | > | | | > | > | > > > > > > > > > > > > > > > > > > > > > | > | < | 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | } }) .orElse("Welcome to your wiki!"); } }.get(); } public record WikiPage(Integer id, String name, String content, Integer version) { } public static final String HOME_PAGE = "HomePage"; @NotNull private static Optional<WikiPage> loadPageContents(final Connection conn, final SearchInterface.SearchResult latestPage) { final int pageId = Integer.parseInt(String.valueOf(latestPage.searchFields().get("wikiPageId").value())); return query(conn, "select * from organization_wiki_pages where id = ?", stmt -> stmt.setInt(1, pageId), rsi -> resultSetStream(rsi, () -> { final var rs = rsi.getResultSet(); return new WikiPage(pageId, rs.getString("page_name"), rs.getString("page_contents"), rs.getInt("version")); }).findFirst()); } private Optional<SearchInterface.SearchResult> findLatestPage(final String orgId, final String pageName) { return search.findDocument(TreePVector.<SearchField>empty() .plus(new SearchField(SEARCH_FIELD_TEAM_ID, orgId)) .plus(new SearchField(SEARCH_FIELD_PAGE_NAME, pageName)) .plus(new SearchField(SEARCH_FIELD_LATEST, true))); } public WikiPage getPage(final String requesterEmail, final String friendlyOrgId, final String pageName) { final var isAbleToSeePage = database.withConnection(true, conn -> userModel.getUserId(conn, requesterEmail) .flatMap(userId -> teamModel.getTeam(requesterEmail, TimeZone.getDefault(), friendlyOrgId)) .map(team -> team.isPubliclyVisible() || team.isRequesterMember()) .orElse(false)); if (isAbleToSeePage) { final var latestPage = findLatestPage(friendlyOrgId, pageName); return latestPage .map(searchResult -> database.withConnection(true, conn -> loadPageContents(conn, searchResult).orElse(null))) .orElseGet(() -> new WikiPage(null, pageName, HOME_PAGE.equals(pageName) ? this.defaultHomeContent : "There is no content here. Yet!", null)); } return null; } public PVector<WikiPage> getBackLinks(final String requesterEmail, final String friendlyOrgId, final String pageName) { final var teamO = database.withConnection(true, conn -> teamModel.getTeam(requesterEmail, TimeZone.getDefault(), friendlyOrgId)); if (teamO.filter(team -> team.isPubliclyVisible() || team.isRequesterMember()).isPresent()) { final var documents = search.findDocuments(TreePVector.<SearchField>empty() .plus(new SearchField(SEARCH_FIELD_TEAM_ID, friendlyOrgId)) .plus(new SearchField(SEARCH_FIELD_CONTENT, pageName, false, true)), 50); return database.withConnection(true, conn -> documents .searchResults() .stream() .map(searchResult -> loadPageContents(conn, searchResult) .orElseThrow(() -> new IllegalStateException(format("Document %s found in index but not database", searchResult.searchFields())))) .reduce(TreePVector.empty(), TreePVector::plus, TreePVector::plusAll)); } else { return TreePVector.empty(); } } public WikiSaveResult savePage(final String requesterEmail, final String friendlyOrgId, final String pageName, final Integer currentVersion, final String pageContents) { final var latestPage = findLatestPage(friendlyOrgId, pageName); return database.withTransaction(conn -> { final var userId = userModel.getUserId(conn, requesterEmail).orElseThrow(); return teamModel.getTeam(requesterEmail, TimeZone.getDefault(), friendlyOrgId) .map(team -> { if (!team.isRequesterMember()) { return WikiSaveResult.accessDenied; } final var nowStr = nowSupplier.get().toString(); final var currentVersionOpt = ofNullable(currentVersion); final var updateResult = latestPage .flatMap(result -> loadPageContents(conn, result)) .map(wikiPage -> currentVersionOpt.map(v -> { if (v.equals(wikiPage.version())) { markPageNotLatest(conn, pageName, team.getId(), wikiPage, nowStr); } else { return WikiSaveResult.collision; } return WikiSaveResult.success; }).orElse(WikiSaveResult.collision)).orElseGet(() -> currentVersionOpt .map(integer -> WikiSaveResult.pageMissing) .orElse(WikiSaveResult.success)); if (updateResult != WikiSaveResult.success) { return updateResult; } |
︙ | ︙ | |||
180 181 182 183 184 185 186 187 | private void indexPage(final String teamId, final Integer pageId, final String pageName, final String pageContents, final int version, final boolean latest) { search.save(TreePVector.<SearchField>empty() | > | | | | | | > > > > > | 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 | private void indexPage(final String teamId, final Integer pageId, final String pageName, final String pageContents, final int version, final boolean latest) { final var processedContents = sanitizeWikiLinks(pageContents); search.save(TreePVector.<SearchField>empty() .plus(new SearchField(SEARCH_FIELD_PAGE_ID, pageId, true, false)) .plus(new SearchField(SEARCH_FIELD_TEAM_ID, teamId, true, false)) .plus(new SearchField(SEARCH_FIELD_PAGE_NAME, pageName, true, false)) .plus(new SearchField(SEARCH_FIELD_VERSION, version, true, false)) .plus(new SearchField(SEARCH_FIELD_LATEST, latest, true, false)) .plus(new SearchField(SEARCH_FIELD_CONTENT, processedContents, false, true))); } private static String sanitizeWikiLinks(final String pageContents) { return WIKI_LINK_PATTERN.matcher(pageContents) .replaceAll(matchResult -> TextSanitizer.wikiWord(matchResult.group(1))); } private void markPageNotLatest(final Connection conn, final String teamId, final String pageName, final WikiPage page, final String nowStr) { |
︙ | ︙ |
Changes to src/main/java/app/coffeetime/web/WikiRouting.java.
︙ | ︙ | |||
65 66 67 68 69 70 71 72 73 74 75 76 77 78 | }); } } catch (InvalidTokenException e) { LOG.get().error("Invalid CSRF token", e); return redirect("/", e.getUserFriendlyMessage()); } })); } @NotNull private static Response wikiResponse(final MergeTool mergeTool, final WikiModel wikiModel, final TeamModel teamModel, final Request req, | > > > > > > > > > > > > > > > > > > | 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | }); } } catch (InvalidTokenException e) { LOG.get().error("Invalid CSRF token", e); return redirect("/", e.getUserFriendlyMessage()); } })); router.GET("/teams/:teamId/wiki/:pageName/_backLinks", wrapAuth(userModel, sessionProperties, req -> { final var teamId = req.requestPropertyAsString("teamId"); final var requesterEmail = req.principal().orElse(null); final var team = teamModel.getTeam(requesterEmail, req.timeZone(), teamId).orElseThrow(); final var pageName = req.requestPropertyAsOptionalString("pageName").orElse(WikiModel.HOME_PAGE); return htmlResponse(renderPage(mergeTool, req, "/templates/web/wikiBackLinks.vm", HashTreePMap.<String, Object>empty() .plus("homePage", WikiModel.HOME_PAGE.equals(pageName)) .plus("homePageName", WikiModel.HOME_PAGE) .plus("pageName", pageName) .plus("team", team) .plus("backLinks", wikiModel.getBackLinks(requesterEmail, teamId, pageName)))); })); } @NotNull private static Response wikiResponse(final MergeTool mergeTool, final WikiModel wikiModel, final TeamModel teamModel, final Request req, |
︙ | ︙ |
Changes to src/main/resources/main.scss.
︙ | ︙ | |||
1928 1929 1930 1931 1932 1933 1934 | grid-row-gap: 1rem; grid-template-columns: min-content; } } #teamsPanel, #teamPanel, #wikiPanel { h1 { | | | | 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 | grid-row-gap: 1rem; grid-template-columns: min-content; } } #teamsPanel, #teamPanel, #wikiPanel { h1 { a:not(.headerText) { font-size: 1rem; line-height: 1rem; vertical-align: top; } } } @media (min-width: 420px) { #teamsPanel, #teamPanel, #wikiPanel { h1 a:not(.headerText) { float: right; margin-left: 0.5rem; } } } @media (max-width: 419px) { |
︙ | ︙ |
Added src/main/resources/templates/web/wikiBackLinks.vm.
> > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <div id="wikiPanel"> <div id="header"> <h1><a class="headerText" href="/teams/${team.id}/wiki/${pageName}">${pageName}</a> references <a href="/teams/${team.id}">${team.name}</a> #if(!${homePage}) <a href="/teams/${team.id}/wiki">${homePageName}</a> #end #if(${team.requesterMember}) <a href="/teams/${team.id}/wiki/${pageName}/_edit">edit</a> #end </h1> </div> <ul> #foreach($page in ${backLinks}) <li> <a class="headerText" href="/teams/${team.id}/wiki/${page.name()}">${page.name()}</a></li> #end </ul> </div> |
Changes to src/main/resources/templates/web/wikiPage.vm.
1 2 | <div id="wikiPanel"> <div id="header"> | | | 1 2 3 4 5 6 7 8 9 10 | <div id="wikiPanel"> <div id="header"> <h1><a class="headerText" href="/teams/${team.id}/wiki/${pageName}/_backLinks">${pageName}</a> <a href="/teams/${team.id}">${team.name}</a> #if(!${homePage}) <a href="/teams/${team.id}/wiki">${homePageName}</a> #end #if(${team.requesterMember}) <a href="/teams/${team.id}/wiki/${pageName}/_edit">edit</a> #end |
︙ | ︙ |
Changes to src/test/java/app/coffeetime/models/WikiModelTest.java.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package app.coffeetime.models; import app.coffeetime.fixtures.TestCoffeeTime; import app.coffeetime.util.Logging; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.ConsoleAppender; import io.sentry.SentryOptions; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.pcollections.HashTreePMap; import org.pcollections.TreePVector; import java.time.Instant; import java.util.TimeZone; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import static app.coffeetime.fixtures.TestCoffeeTime.ADMIN_EMAIL; import static org.junit.jupiter.api.Assertions.*; public class WikiModelTest { private static final Instant EXPECTED_NOW = Instant.parse("2024-06-10T12:30:00Z"); private static final AtomicReference<Instant> CURRENT_NOW = new AtomicReference<>(); private static final String DEFAULT_HOME_CONTENT_SNIPPET = "Welcome to your wiki!"; private static final String DEFAULT_NON_HOME_CONTENT_SNIPPET = "There is no content here"; | > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package app.coffeetime.models; import app.coffeetime.fixtures.TestCoffeeTime; import app.coffeetime.util.Logging; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.ConsoleAppender; import io.sentry.SentryOptions; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.util.AttributeImpl; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.pcollections.HashTreePMap; import org.pcollections.TreePVector; import java.time.Instant; import java.util.Iterator; import java.util.TimeZone; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import static app.coffeetime.fixtures.TestCoffeeTime.ADMIN_EMAIL; import static app.coffeetime.models.WikiModel.HOME_PAGE; import static org.junit.jupiter.api.Assertions.*; public class WikiModelTest { private static final Instant EXPECTED_NOW = Instant.parse("2024-06-10T12:30:00Z"); private static final AtomicReference<Instant> CURRENT_NOW = new AtomicReference<>(); private static final String DEFAULT_HOME_CONTENT_SNIPPET = "Welcome to your wiki!"; private static final String DEFAULT_NON_HOME_CONTENT_SNIPPET = "There is no content here"; |
︙ | ︙ | |||
86 87 88 89 90 91 92 | teamModel.addMember(ADMIN_EMAIL, this.publicTeam.getId(), nonAdminMemberUser.userInfo()); teamModel.addMember(ADMIN_EMAIL, this.privateTeam.getId(), nonAdminMemberUser.userInfo()); } @Test public void missingHomePage() { | | | | | > | | | | | | | | | | > | 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | teamModel.addMember(ADMIN_EMAIL, this.publicTeam.getId(), nonAdminMemberUser.userInfo()); teamModel.addMember(ADMIN_EMAIL, this.privateTeam.getId(), nonAdminMemberUser.userInfo()); } @Test public void missingHomePage() { final var page = wikiModel.getPage(ADMIN_EMAIL, publicTeam.getId(), HOME_PAGE); assertTrue(page.content().contains(DEFAULT_HOME_CONTENT_SNIPPET)); assertNull(page.version()); } @Test public void newHomePage() { final var expectedContent = "This is our wiki"; wikiModel.savePage(ADMIN_EMAIL, publicTeam.getId(), HOME_PAGE, null, expectedContent); final var secondTeamHome = wikiModel.getPage(ADMIN_EMAIL, privateTeam.getId(), HOME_PAGE); final var page = wikiModel.getPage(ADMIN_EMAIL, publicTeam.getId(), HOME_PAGE); assertTrue(secondTeamHome.content().contains(DEFAULT_HOME_CONTENT_SNIPPET), secondTeamHome.toString()); assertEquals(HOME_PAGE, page.name()); assertEquals(expectedContent, page.content()); assertEquals(1, page.version()); } @Test public void existingPage() { final var expectedContent = "This is our wiki"; wikiModel.savePage(ADMIN_EMAIL, publicTeam.getId(), HOME_PAGE, null, "First update ignored"); wikiModel.savePage(ADMIN_EMAIL, publicTeam.getId(), HOME_PAGE, 1, expectedContent); final var secondPage = wikiModel.getPage(ADMIN_EMAIL, publicTeam.getId(), HOME_PAGE); assertEquals(expectedContent, secondPage.content()); assertEquals(2, secondPage.version()); } @Test public void conflictingUpdatesRejected() { final var expectedContent = "This is our wiki"; wikiModel.savePage(ADMIN_EMAIL, publicTeam.getId(), HOME_PAGE, null, "First update ignored"); final var firstResult = wikiModel.savePage(ADMIN_EMAIL, publicTeam.getId(), HOME_PAGE, 1, expectedContent); final var secondResult = wikiModel.savePage(ADMIN_EMAIL, publicTeam.getId(), HOME_PAGE, 1, "Bad update"); final var thirdResult = wikiModel.savePage(ADMIN_EMAIL, publicTeam.getId(), HOME_PAGE, null, "Bad update"); final var secondPage = wikiModel.getPage(ADMIN_EMAIL, publicTeam.getId(), HOME_PAGE); assertEquals(WikiModel.WikiSaveResult.success, firstResult); assertEquals(WikiModel.WikiSaveResult.collision, secondResult); assertEquals(WikiModel.WikiSaveResult.collision, thirdResult); assertEquals(expectedContent, secondPage.content()); assertEquals(2, secondPage.version()); } @Test public void nonexistentPreviousVersion() { final var teamId = publicTeam.getId(); final var saveResult = wikiModel.savePage(ADMIN_EMAIL, teamId, HOME_PAGE, 2, "Bad save"); final var page = wikiModel.getPage(ADMIN_EMAIL, publicTeam.getId(), HOME_PAGE); assertEquals(WikiModel.WikiSaveResult.pageMissing, saveResult); assertTrue(page.content().contains(DEFAULT_HOME_CONTENT_SNIPPET)); assertNull(page.version()); } @Test public void updateNotHomePage() { final var pageName = "NotHome"; final var expectedContent = "This is our wiki"; wikiModel.savePage(ADMIN_EMAIL, publicTeam.getId(), pageName, null, expectedContent); final var secondTeamPage = wikiModel.getPage(ADMIN_EMAIL, privateTeam.getId(), pageName); final var page = wikiModel.getPage(ADMIN_EMAIL, publicTeam.getId(), pageName); assertTrue(secondTeamPage.content().contains(DEFAULT_NON_HOME_CONTENT_SNIPPET), secondTeamPage.toString()); assertEquals(pageName, page.name()); assertEquals(expectedContent, page.content()); assertEquals(1, page.version()); } @Test public void nonAdminsCanUpdateAndView() { final var expectedPublicContent = "This is our public wiki"; |
︙ | ︙ | |||
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 | assertNull(wikiModel.getPage(null, publicTeam.getId(), pageName)); assertNull(wikiModel.getPage(null, privateTeam.getId(), pageName)); } @Test public void backLinks() { } @Test public void searchAmongLatestPages() { } | > > > > > > > > > > > > > > > > > > > > > > > > > > | 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | assertNull(wikiModel.getPage(null, publicTeam.getId(), pageName)); assertNull(wikiModel.getPage(null, privateTeam.getId(), pageName)); } @Test public void backLinks() { final var expectedPublicContents = "This is a public page that refers to [[AnotherPage]] that is of note"; final var expectedPrivateContents = "This is a private page that refers to [[Another Page]] that is of note"; wikiModel.savePage(NON_ADMIN_TEAM_MEMBER, publicTeam.getId(), "HomePage", null, "This is a page without any links"); final var publicLinkedResult = wikiModel.savePage(NON_ADMIN_TEAM_MEMBER, publicTeam.getId(), "HomePage", 1, expectedPublicContents); final var privateLinkedResult = wikiModel.savePage(NON_ADMIN_TEAM_MEMBER, privateTeam.getId(), "HomePage", null, expectedPrivateContents); wikiModel.savePage(NON_ADMIN_TEAM_MEMBER, publicTeam.getId(), "AnotherPage", null, "This is another fun page"); final var publicBackLinks = wikiModel.getBackLinks(NON_TEAM_MEMBER, publicTeam.getId(), "AnotherPage"); final var privateBackLinks = wikiModel.getBackLinks(ADMIN_EMAIL, privateTeam.getId(), "AnotherPage"); final var notAllowedBackLinks = wikiModel.getBackLinks(NON_TEAM_MEMBER, privateTeam.getId(), "AnotherPage"); assertEquals(WikiModel.WikiSaveResult.success, publicLinkedResult); assertEquals(WikiModel.WikiSaveResult.success, privateLinkedResult); assertEquals(1, publicBackLinks.size()); assertEquals(1, privateBackLinks.size()); assertEquals(0, notAllowedBackLinks.size()); assertEquals(expectedPublicContents, publicBackLinks.getFirst().content()); assertEquals(HOME_PAGE, publicBackLinks.getFirst().name()); assertEquals(expectedPrivateContents, privateBackLinks.getFirst().content()); assertEquals(HOME_PAGE, privateBackLinks.getFirst().name()); } @Test public void searchAmongLatestPages() { } |
︙ | ︙ |