| /******************************************************************************* |
| * Copyright (c) 2010-2014 SAP AG and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * SAP AG - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.skalli.core.rest; |
| |
| import static org.junit.Assert.fail; |
| |
| import java.text.MessageFormat; |
| import java.util.Set; |
| import java.util.regex.Pattern; |
| |
| import org.custommonkey.xmlunit.Difference; |
| import org.custommonkey.xmlunit.DifferenceConstants; |
| import org.custommonkey.xmlunit.DifferenceListener; |
| import org.custommonkey.xmlunit.NodeDetail; |
| import org.eclipse.skalli.commons.CollectionUtils; |
| import org.w3c.dom.Node; |
| |
| @SuppressWarnings("nls") |
| public class ProjectDetailsV1V2Diff extends ProjectsV1V2Diff implements DifferenceListener { |
| |
| private final Pattern MEMBER_XPATH_PATTERN = getPattern("/members\\[1\\]/member\\[\\d+\\]/link\\[1\\]/@href"); |
| private final Pattern PEOPLE_EXT_LEADS_XPATH_PATTERN = getExtPattern("/people\\[1\\]/leads\\[1\\]/lead\\[\\d+\\]/link\\[1\\]/@href"); |
| private final Pattern PEOPLE_EXT_MEMBERS_XPATH_PATTERN = getExtPattern("/people\\[1\\]/members\\[1\\]/member\\[\\d+\\]/link\\[1\\]/@href"); |
| private final Pattern INFO_EXT_XPATH_PATTERN = getExtPattern("/info\\[1\\]"); |
| private final Pattern INFO_EXT_MAILINGLISTS_XPATH_PATTERN = getExtPattern("/info\\[1\\]/mailingLists\\[1\\]"); |
| private final Pattern DEVINF_EXT_XPATH_PATTERN = getExtPattern("/devInf\\[1\\]"); |
| private final Pattern DEVINF_EXT_JAVADOCS_XPATH_PATTERN = getExtPattern("/devInf\\[1\\]/javadocs\\[1\\]"); |
| private final Pattern DEVINF_EXT_SCMLOCATIONS_XPATH_PATTERN = getExtPattern("/devInf\\[1\\]/scmLocations\\[1\\]"); |
| private final Pattern MAVEN_EXT_COORDINATE_XPATH_PATTERN = getExtPattern("/mavenReactor\\[1\\]/coordinate\\[1\\]"); |
| private final Pattern MAVEN_EXT_VERSIONS_XPATH_PATTERN = getExtPattern("/mavenReactor\\[1\\]/coordinate\\[1\\]/versions\\[1\\]"); |
| private final Pattern MAVEN_EXT_PACKAGING_XPATH_PATTERN = getExtPattern("/mavenReactor\\[1\\]/coordinate\\[1\\]/packaging\\[1\\]"); |
| private final Pattern MAVEN_EXT_MODULE_XPATH_PATTERN = getExtPattern("/mavenReactor\\[1\\]/modules\\[1\\]/module\\[\\d+\\]"); |
| private final Pattern MAVEN_EXT_MODULE_VERSIONS_PATTERN = getExtPattern("/mavenReactor\\[1\\]/modules\\[1\\]/module\\[\\d+\\]/versions\\[1\\]"); |
| private final Pattern MAVEN_EXT_MODULE_PACKAGING_XPATH_PATTERN = getExtPattern("/mavenReactor\\[1\\]/modules\\[1\\]/module\\[\\d+\\]/packaging\\[1\\]"); |
| private final Pattern RELATED_EXT_XPATH_PATTERN = getPattern("/extensions\\[1\\]/relatedProjects\\[1\\]"); |
| private final Pattern RELATED_EXT_CALCULATED_XPATH_PATTERN = getExtPattern("/relatedProjects\\[1\\]/calculated\\[1\\]"); |
| private final Pattern RELATED_EXT_LINK_XPATH_PATTERN = getExtPattern("/relatedProjects\\[1\\]/link\\[\\d+\\]"); |
| private final Pattern REVIEWS_EXT_XPATH_PATTERN = getExtPattern("/reviews\\[1\\]"); |
| private final Pattern REVIEWS_EXT_REVIEW_XPATH_PATTERN = getExtPattern("/reviews\\[1\\]/review\\[\\d+\\]"); |
| private final Pattern REVIEWS_EXT_STYLE_XPATH_PATTERN = getExtPattern("/reviews\\[1\\]/ratingStyle\\[1\\]"); |
| private final Pattern REVIEWS_EXT_ANONYMOUS_XPATH_PATTERN = getExtPattern("/reviews\\[1\\]/allowAnonymous\\[1\\]"); |
| private final Pattern REVIEWS_EXT_NUMBERVOTES_XPATH_PATTERN = getExtPattern("/reviews\\[1\\]/numberVotes\\[1\\]"); |
| private final Pattern REVIEWS_EXT_NUMBERUPS_XPATH_PATTERN = getExtPattern("/reviews\\[1\\]/numberThumbsUp\\[1\\]"); |
| private final Pattern REVIEWS_EXT_NUMBERDNS_XPATH_PATTERN = getExtPattern("/reviews\\[1\\]/numberThumbsDown\\[1\\]"); |
| private final Pattern REVIEWS_EXT_AVGRATING_XPATH_PATTERN = getExtPattern("/reviews\\[1\\]/averageRating\\[1\\]"); |
| private final Pattern REVIEWS_EXT_COMMENT_TEXT_XPATH_PATTERN = getExtPattern("/reviews\\[1\\]/review\\[\\d+\\]/comment\\[1\\]/text\\(\\)\\[1\\]"); |
| private final Pattern SCRUM_EXT_XPATH_PATTERN = getExtPattern("/scrum\\[1\\]"); |
| private final Pattern SCRUM_EXT_SCRUMMASTERAXPATH_PATTERN = getExtPattern("/scrum\\[1\\]/scrumMasters\\[1\\]"); |
| private final Pattern SCRUM_EXT_PRODUCTOWNERS_XPATH_PATTERN = getExtPattern("/scrum\\[1\\]/productOwners\\[1\\]"); |
| |
| private static final Set<String> ADDITIONAL_REVIEW_TAGS = CollectionUtils.asSet("ratingStyle", "allowAnonymous", |
| "numberVotes", "numberThumbsUp", "numberThumbsDown", "averageRating"); |
| |
| private final Pattern USER_PATTERN; |
| private final Pattern USERS_PATTERN; |
| |
| public ProjectDetailsV1V2Diff(String webLocator) { |
| super(webLocator); |
| USER_PATTERN = Pattern.compile("^" + webLocator + "/api/user/.+$"); |
| USERS_PATTERN = Pattern.compile("^" + webLocator + "/api/users/.+$"); |
| } |
| |
| @Override |
| public int differenceFound(Difference difference) { |
| int result = super.differenceFound(difference); |
| if (result == RETURN_ACCEPT_DIFFERENCE) { |
| NodeDetail expected = difference.getControlNodeDetail(); |
| NodeDetail actual = difference.getTestNodeDetail(); |
| switch (difference.getId()) { |
| case DifferenceConstants.ATTR_VALUE_ID: |
| // path prefix of the user API has been changed from /api/user to /api/users: |
| // all <link> tags to the user API have changed from v1 to v2; this affects |
| // the <members> section and the <people> extension |
| if (equalsAndMatchesAnyXPath(expected, actual, MEMBER_XPATH_PATTERN, |
| PEOPLE_EXT_LEADS_XPATH_PATTERN, PEOPLE_EXT_MEMBERS_XPATH_PATTERN) |
| && USER_PATTERN.matcher(expected.getValue()).matches() |
| && USERS_PATTERN.matcher(actual.getValue()).matches()) { |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } |
| break; |
| case DifferenceConstants.HAS_CHILD_NODES_ID: |
| if (equalsAndMatchesAnyXPath(expected, actual, INFO_EXT_XPATH_PATTERN) |
| && hasEmptyChildNode(actual, "mailingLists")) { |
| // collection-like tags are always rendered in the v2 API even if empty; |
| // in the v1 API they were not rendered at all |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } else if (equalsAndMatchesAnyXPath(expected, actual, DEVINF_EXT_XPATH_PATTERN) |
| && hasEmptyChildNode(actual, "scmLocations")) { |
| // collection-like tags are always rendered in the v2 API even if empty; |
| // in the v1 API they were not rendered at all |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } else if (equalsAndMatchesAnyXPath(expected, actual, SCRUM_EXT_XPATH_PATTERN) |
| && hasAnyChildNode(actual, CollectionUtils.asSet("scrumMasters", "productOwners"))) { |
| // the v1 API did not rendered the scrum masters and product owners at all; |
| // the v2 API renders these lists similiar to the <people> extension (userId + link) |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } else if (equalsAndMatchesAnyXPath(expected, actual, RELATED_EXT_XPATH_PATTERN) |
| && hasBooleanChildNode(actual, "calculated")) { |
| // the v1 API rendered the <calculated> tag only in the case the value is true; |
| // the v2 API renders this tag always with the corresponding value |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } else if (equalsAndMatchesAnyXPath(expected, actual, REVIEWS_EXT_XPATH_PATTERN) |
| && hasAnyChildNode(actual, ADDITIONAL_REVIEW_TAGS)) { |
| // the v2 API renders several additional tags (see ADDITIONAL_REVIEW_TAGS) |
| // that are not present in the v1 API |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } |
| break; |
| case DifferenceConstants.CHILD_NODELIST_LENGTH_ID: |
| if (equalsAndMatchesAnyXPath(expected, actual, INFO_EXT_XPATH_PATTERN) |
| && hasEmptyChildNode(actual, "mailingLists")) { |
| // collection-like tags are always rendered in the v2 API even if empty; |
| // in the v1 API they were not rendered at all |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } else if (equalsAndMatchesAnyXPath(expected, actual, DEVINF_EXT_XPATH_PATTERN) |
| && hasEmptyChildNode(actual, "javadocs")) { |
| // collection-like tags are always rendered in the v2 API even if empty; |
| // in the v1 API they were not rendered at all |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } else if (equalsAndMatchesAnyXPath(expected, actual, MAVEN_EXT_COORDINATE_XPATH_PATTERN, MAVEN_EXT_MODULE_XPATH_PATTERN) |
| && hasEmptyChildNode(actual, "versions")) { |
| // collection-like tags are always rendered in the v2 API even if empty; |
| // in the v1 API they were not rendered at all |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } else if (equalsAndMatchesAnyXPath(expected, actual, SCRUM_EXT_XPATH_PATTERN) |
| && hasAnyChildNode(actual, CollectionUtils.asSet("scrumMasters", "productOwners"))) { |
| // the v1 API did not rendered the scrum masters and product owners at all; |
| // the v2 API renders these lists similiar to the <people> extension (userId + link) |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } else if (equalsAndMatchesAnyXPath(expected, actual, RELATED_EXT_XPATH_PATTERN) |
| && hasBooleanChildNode(actual, "calculated")) { |
| // the v1 API rendered the <calculated> tag only in the case the value is true; |
| // the v2 API renders this tag always with the corresponding value |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } else if (equalsAndMatchesAnyXPath(expected, actual, REVIEWS_EXT_XPATH_PATTERN) |
| && hasAnyChildNode(actual, ADDITIONAL_REVIEW_TAGS)) { |
| // the v2 API renders several additional tags (see ADDITIONAL_REVIEW_TAGS) |
| // that are not present in the v1 API |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } |
| break; |
| case DifferenceConstants.CHILD_NODE_NOT_FOUND_ID: |
| if (matchesAnyXPath(actual, INFO_EXT_MAILINGLISTS_XPATH_PATTERN) |
| && "mailingLists".equals(actual.getValue())) { |
| // collection-like tags are always rendered in the v2 API even if empty; |
| // in the v1 API they were not rendered at all |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } else if (matchesAnyXPath(actual, DEVINF_EXT_JAVADOCS_XPATH_PATTERN) |
| && "javadocs".equals(actual.getValue())) { |
| // collection-like tags are always rendered in the v2 API even if empty; |
| // in the v1 API they were not rendered at all |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } else if (matchesAnyXPath(actual, DEVINF_EXT_SCMLOCATIONS_XPATH_PATTERN) |
| && "scmLocations".equals(actual.getValue())) { |
| // collection-like tags are always rendered in the v2 API even if empty; |
| // in the v1 API they were not rendered at all |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } else if (matchesAnyXPath(actual, MAVEN_EXT_VERSIONS_XPATH_PATTERN, MAVEN_EXT_MODULE_VERSIONS_PATTERN) |
| && "versions".equals(actual.getValue())) { |
| // collection-like tags are always rendered in the v2 API even if empty; |
| // in the v1 API they were not rendered at all |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } else if (matchesAnyXPath(actual, SCRUM_EXT_SCRUMMASTERAXPATH_PATTERN) |
| && "scrumMasters".equals(actual.getValue())) { |
| // the v1 API did not rendered the scrum masters and product owners at all; |
| // the v2 API renders these lists similiar to the <people> extension (userId + link) |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } else if (matchesAnyXPath(actual, SCRUM_EXT_PRODUCTOWNERS_XPATH_PATTERN) |
| && "productOwners".equals(actual.getValue())) { |
| // the v1 API did not rendered the scrum masters and product owners at all; |
| // the v2 API renders these lists similiar to the <people> extension (userId + link) |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } else if (matchesAnyXPath(actual, RELATED_EXT_CALCULATED_XPATH_PATTERN) |
| && "calculated".equals(actual.getValue())) { |
| // the v1 API rendered the <calculated> tag only in the case the value is true; |
| // the v2 API renders this tag always with the corresponding value |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } else if (matchesAnyXPath(actual, REVIEWS_EXT_STYLE_XPATH_PATTERN, REVIEWS_EXT_ANONYMOUS_XPATH_PATTERN, |
| REVIEWS_EXT_NUMBERVOTES_XPATH_PATTERN, REVIEWS_EXT_NUMBERUPS_XPATH_PATTERN, |
| REVIEWS_EXT_NUMBERDNS_XPATH_PATTERN, REVIEWS_EXT_AVGRATING_XPATH_PATTERN) |
| && ADDITIONAL_REVIEW_TAGS.contains(actual.getValue())) { |
| // the v2 API renders several additional tags (see ADDITIONAL_REVIEW_TAGS) |
| // that are not present in the v1 API |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } |
| break; |
| case DifferenceConstants.CHILD_NODELIST_SEQUENCE_ID: |
| if (equalsAndMatchesAnyXPath(expected, actual, MAVEN_EXT_PACKAGING_XPATH_PATTERN, |
| MAVEN_EXT_MODULE_PACKAGING_XPATH_PATTERN, RELATED_EXT_LINK_XPATH_PATTERN)) { |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } else if (equalsAndMatchesAnyXPath(expected, actual, REVIEWS_EXT_REVIEW_XPATH_PATTERN)) { |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| } |
| break; |
| case DifferenceConstants.TEXT_VALUE_ID: |
| // old and new API render different file endings: the new API renders a single \n #xA), |
| // while the old API preferred \r\n (#xD #xA) |
| if (equalsAndMatchesAnyXPath(expected, actual, REVIEWS_EXT_COMMENT_TEXT_XPATH_PATTERN) |
| && equalsValueIgnoreLineEndings(expected, actual)) { |
| result = RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; |
| }; |
| break; |
| default: |
| result = RETURN_ACCEPT_DIFFERENCE; |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public void skippedComparison(Node expected, Node actual) { |
| fail(MessageFormat.format( |
| "comparison skipped because the node types are not comparable: {0} (type: {1}) - {2} (type: {3})", |
| expected.getNodeName(), expected.getNodeType(), actual.getNodeName(), actual.getNodeType())); |
| |
| } |
| |
| @Override |
| protected String getRootPath() { |
| return ""; |
| } |
| } |