package hudson.plugins.jobConfigHistory;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.Assert;
import org.jvnet.hudson.test.recipes.LocalData;
import com.gargoylesoftware.htmlunit.TextPage;
import com.gargoylesoftware.htmlunit.WebAssert;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlButton;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import hudson.model.FreeStyleProject;
import hudson.security.HudsonPrivateSecurityRealm;
import hudson.security.LegacyAuthorizationStrategy;
public class JobConfigHistoryRootActionIT
extends
AbstractHudsonTestCaseDeletingInstanceDir {
private WebClient webClient;
// we need to sleep between saves so we don't overwrite the history
// directories
// (which are saved with a granularity of one second)
private static final int SLEEP_TIME = 1100;
@Override
public void before() throws Throwable {
super.before();
webClient = createWebClient();
Logger.getLogger("com.gargoylesoftware.htmlunit").setLevel(Level.OFF);
Logger.getLogger("").setLevel(Level.WARNING);
Logger.getLogger(this.getClass().getPackage().getName())
.setLevel(Level.INFO);
webClient.setJavaScriptEnabled(true);
webClient.setCssEnabled(false);
}
/**
* Tests whether info gets displayed correctly for filter parameter
* none/system/jobs/deleted/created.
*/
public void testFilterWithData() throws Exception {
// create some config history data
final FreeStyleProject project = createFreeStyleProject("Test1");
Thread.sleep(SLEEP_TIME);
project.disable();
Thread.sleep(SLEEP_TIME);
jenkins.setSystemMessage("Testmessage");
Thread.sleep(SLEEP_TIME);
final FreeStyleProject secondProject = createFreeStyleProject("Test2");
Thread.sleep(SLEEP_TIME);
secondProject.delete();
// check page with system history entries
checkSystemPage(webClient.goTo(JobConfigHistoryConsts.URLNAME));
checkSystemPage(webClient
.goTo(JobConfigHistoryConsts.URLNAME + "/?filter=system"));
// check page with job history entries
final HtmlPage htmlPageJobs = webClient
.goTo(JobConfigHistoryConsts.URLNAME + "/?filter=jobs");
Assert.assertTrue("Verify history entry for job is listed.",
htmlPageJobs.getAnchorByText("Test1") != null);
final String htmlPageJobsBody = htmlPageJobs.asXml();
Assert.assertTrue("Verify history entry for deleted job is listed.",
htmlPageJobsBody.contains(DeletedFileFilter.DELETED_MARKER));
Assert.assertFalse(
"Verify that no history entry for system change is listed.",
htmlPageJobsBody.contains("config (system)"));
Assert.assertTrue("Check link to job page.", htmlPageJobsBody
.contains("job/Test1/" + JobConfigHistoryConsts.URLNAME));
// check page with 'created' history entries
final HtmlPage htmlPageCreated = webClient
.goTo("jobConfigHistory/?filter=created");
Assert.assertTrue("Verify history entry for job is listed.",
htmlPageCreated.getAnchorByText("Test1") != null);
Assert.assertFalse(
"Verify history entry for deleted job is not listed.",
htmlPageCreated.asText()
.contains(DeletedFileFilter.DELETED_MARKER));
Assert.assertFalse(
"Verify that no history entry for system change is listed.",
htmlPageCreated.asText().contains("config (system)"));
Assert.assertTrue("Check link to job page exists.", htmlPageJobs.asXml()
.contains("job/Test1/" + JobConfigHistoryConsts.URLNAME));
Assert.assertFalse("Verify that only \'Created\' entries are listed.",
htmlPageCreated.asXml().contains("Deleted</td>")
|| htmlPageCreated.asXml().contains("Changed</td>"));
// check page with 'deleted' history entries
final HtmlPage htmlPageDeleted = webClient
.goTo("jobConfigHistory/?filter=deleted");
final String page = htmlPageDeleted.asXml();
System.out.println(page);
Assert.assertTrue("Verify history entry for deleted job is listed.",
page.contains(DeletedFileFilter.DELETED_MARKER));
Assert.assertFalse(
"Verify no history entry for existing job is listed.",
page.contains("Test1"));
Assert.assertFalse(
"Verify no history entry for system change is listed.",
page.contains("(system)"));
Assert.assertTrue("Check link to historypage exists.",
page.contains("history?name"));
Assert.assertFalse("Verify that only \'Deleted\' entries are listed.",
page.contains("Created</td>") || page.contains("Changed</td>"));
}
/**
* Checks whether system config history is displayed correctly.
*
* @param htmlPage
*/
private void checkSystemPage(HtmlPage htmlPage) {
final String page = htmlPage.asXml();
System.out.println(page);
Assert.assertTrue("Verify history entry for system change is listed.",
htmlPage.getAnchorByText("config") != null);
Assert.assertFalse("Verify no job history entry is listed.",
page.contains("Test1"));
Assert.assertFalse("Verify history entry for deleted job is listed.",
page.contains(DeletedFileFilter.DELETED_MARKER));
Assert.assertTrue("Check link to historypage exists.",
page.contains("history?name"));
}
/**
* System config history should only be visible with the right permissions.
*/
public void testFilterWithoutPermissions() {
jenkins.setSecurityRealm(
new HudsonPrivateSecurityRealm(false, false, null));
jenkins.setAuthorizationStrategy(new LegacyAuthorizationStrategy());
try {
final HtmlPage htmlPage = webClient
.goTo(JobConfigHistoryConsts.URLNAME);
Assert.assertTrue("Verify nothing is shown without permission",
htmlPage.asText().contains("No permission to view"));
} catch (Exception ex) {
Assert.fail(
"Unable to complete testFilterWithoutPermissions: " + ex);
}
}
/**
* Tests whether the config history of a single system feature is displayed
* correctly and showDiffs works.
*/
public void testSingleSystemHistoryPage() {
final String firstMessage = "First Testmessage";
final String secondMessage = "Second Testmessage";
// create some config history data
try {
jenkins.setSystemMessage(firstMessage);
Thread.sleep(SLEEP_TIME);
jenkins.setSystemMessage(secondMessage);
Thread.sleep(SLEEP_TIME);
} catch (Exception ex) {
Assert.fail("Unable to prepare Jenkins instance: " + ex);
}
try {
final HtmlPage htmlPage = webClient.goTo(
JobConfigHistoryConsts.URLNAME + "/history?name=config");
final String page = htmlPage.asXml();
System.out.println(page);
Assert.assertFalse("Check whether configuration data is found.",
page.contains("No configuration history"));
Assert.assertTrue(
"Verify several entries for config changes exist.",
page.split("Changed").length > 2);
final HtmlForm diffFilesForm = htmlPage.getFormByName("diffFiles");
final HtmlPage diffPage = (HtmlPage) diffFilesForm
.submit((HtmlButton) last(
diffFilesForm.getHtmlElementsByTagName("button")));
assertStringContains(diffPage.asText(), firstMessage);
assertStringContains(diffPage.asText(), secondMessage);
} catch (Exception ex) {
Assert.fail("Unable to complete testHistoryPage: " + ex);
}
}
/**
* Tests whether config history of single deleted job is displayed
* correctly.
*/
public void testSingleDeletedJobHistoryPage() {
// create some config history data
try {
final FreeStyleProject project = createFreeStyleProject("Test");
Thread.sleep(SLEEP_TIME);
project.delete();
} catch (Exception ex) {
Assert.fail("Unable to prepare Jenkins instance: " + ex);
}
try {
final HtmlPage htmlPage = webClient
.goTo(JobConfigHistoryConsts.URLNAME + "/?filter=deleted");
final HtmlAnchor deletedLink = (HtmlAnchor) htmlPage
.getElementById("deleted");
final String historyPage = ((HtmlPage) deletedLink.click()).asXml();
Assert.assertFalse("Check whether configuration data is found.",
historyPage.contains("No configuration history"));
Assert.assertTrue("Verify entry for creation exists.",
historyPage.contains("Created"));
Assert.assertTrue("Verify entry for deletion exists.",
historyPage.contains("Deleted"));
} catch (Exception ex) {
Assert.fail("Unable to complete testHistoryPage: " + ex);
}
}
public void testGetOldConfigXmlWithWrongParameters() {
final JobConfigHistoryRootAction rootAction = new JobConfigHistoryRootAction();
try {
rootAction.getOldConfigXml("bla", "bogus");
Assert.fail("Expected " + IllegalArgumentException.class
+ " because of invalid timestamp.");
} catch (IllegalArgumentException e) {
System.err.println(e);
}
try {
final String timestamp = new SimpleDateFormat(
JobConfigHistoryConsts.ID_FORMATTER)
.format(new GregorianCalendar().getTime());
rootAction.getOldConfigXml("bla..blubb", timestamp);
Assert.fail("Expected " + IllegalArgumentException.class
+ " because of '..' in parameter name.");
} catch (IllegalArgumentException e) {
System.err.println(e);
}
}
public void testDeletedAfterDisabled() throws Exception {
final String description = "All your base";
final FreeStyleProject project = createFreeStyleProject("Test");
project.setDescription(description);
Thread.sleep(SLEEP_TIME);
project.delete();
final HtmlPage htmlPage = webClient
.goTo(JobConfigHistoryConsts.URLNAME + "/?filter=deleted");
final HtmlAnchor rawLink = (HtmlAnchor) htmlPage
.getAnchorByText("(RAW)");
final String rawPage = ((TextPage) rawLink.click()).getContent();
Assert.assertTrue("Verify config file is shown",
rawPage.contains(description));
}
/**
* Tests if restoring a project that was disabled before deletion works.
*
* @throws Exception
*/
public void testRestoreAfterDisabled() throws Exception {
final String description = "bla";
final String name = "TestProject";
final FreeStyleProject project = createFreeStyleProject(name);
project.setDescription(description);
project.disable();
Thread.sleep(SLEEP_TIME);
project.delete();
final HtmlPage jobPage = restoreProject();
WebAssert.assertTextPresent(jobPage, name);
WebAssert.assertTextPresent(jobPage, description);
final HtmlPage historyPage = webClient
.goTo("job/" + name + "/" + JobConfigHistoryConsts.URLNAME);
final String historyAsXml = historyPage.asXml();
System.out.println(historyAsXml);
Assert.assertTrue("History page should contain 'Deleted' entry",
historyAsXml.contains("Deleted"));
final List<HtmlAnchor> hrefs = historyPage
.getByXPath("//a[contains(@href, \"configOutput?type=xml\")]");
Assert.assertTrue(hrefs.size() > 2);
}
/**
* Tests whether finding a new name for a restored project works if the old
* name is already occupied.
*
* @throws Exception
*/
public void testRestoreWithSameName() throws Exception {
final String description = "blubb";
final String name = "TestProject";
final FreeStyleProject project = createFreeStyleProject(name);
project.setDescription(description);
Thread.sleep(SLEEP_TIME);
project.delete();
createFreeStyleProject(name);
final HtmlPage jobPage = restoreProject();
WebAssert.assertTextPresent(jobPage, description);
WebAssert.assertTextPresent(jobPage, name + "_1");
}
/**
* Tests that project gets restored even without previous configs, because
* there is one saved at the time of deletion.
*
* @throws Exception
*/
@LocalData
public void testRestoreWithoutConfigs() throws Exception {
final String name = "JobWithNoConfigHistory";
final FreeStyleProject project = (FreeStyleProject) jenkins
.getItem(name);
final String description = project.getDescription();
Thread.sleep(SLEEP_TIME);
project.delete();
final HtmlPage jobPage = restoreProject();
WebAssert.assertTextPresent(jobPage, name);
WebAssert.assertTextPresent(jobPage, description);
}
/**
* A project will not be restored if there are no configs present and it has
* been disabled at the time of deletion.
*
* @throws Exception
*/
@LocalData
public void testNoRestoreLinkWhenNoConfigs() throws Exception {
final String name = "DisabledJobWithNoConfigHistory";
final FreeStyleProject project = (FreeStyleProject) jenkins
.getItem(name);
Thread.sleep(SLEEP_TIME);
project.delete();
final HtmlPage htmlPage = webClient
.goTo(JobConfigHistoryConsts.URLNAME + "/?filter=deleted");
WebAssert.assertElementNotPresentByXPath(htmlPage,
("//img[contains(@src, \"restore.png\")]"));
}
private HtmlPage restoreProject() throws Exception {
final HtmlPage htmlPage = webClient
.goTo(JobConfigHistoryConsts.URLNAME + "/?filter=deleted");
final HtmlAnchor restoreLink = (HtmlAnchor) htmlPage
.getElementById("restore");
final HtmlPage reallyRestorePage = (HtmlPage) restoreLink.click();
final HtmlForm restoreForm = reallyRestorePage.getFormByName("restore");
final HtmlPage jobPage = submit(restoreForm, "Submit");
return jobPage;
}
/**
* Tests whether the 'Restore project' button on the history page works as
* well.
*
* @throws Exception
*/
public void testRestoreFromHistoryPage() throws Exception {
final String description = "All your base";
final String name = "TestProject";
final FreeStyleProject project = createFreeStyleProject(name);
project.setDescription(description);
Thread.sleep(SLEEP_TIME);
project.delete();
final HtmlPage htmlPage = webClient
.goTo(JobConfigHistoryConsts.URLNAME + "/?filter=deleted");
final List<HtmlAnchor> hrefs = htmlPage
.getByXPath("//a[contains(@href, \"TestProject_deleted_\")]");
final HtmlPage historyPage = (HtmlPage) hrefs.get(0).click();
final HtmlPage reallyRestorePage = submit(
historyPage.getFormByName("forward"), "Submit");
final HtmlPage jobPage = submit(
reallyRestorePage.getFormByName("restore"), "Submit");
WebAssert.assertTextPresent(jobPage, name);
WebAssert.assertTextPresent(jobPage, description);
}
}