All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.simplity.test.TestCase Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2016 simplity.org
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package org.simplity.test;

import java.util.Date;

import org.simplity.json.JSONArray;
import org.simplity.json.JSONObject;
import org.simplity.kernel.Tracer;
import org.simplity.kernel.comp.ValidationContext;
import org.simplity.service.ServiceProtocol;

/**
 * Represents one test case for a service with one specific input and expected
 * output
 *
 * @author simplity.org
 *
 */
public class TestCase {
	/**
	 * unique name given to this test case.
	 */
	String testCaseName;

	/**
	 * service to be tested
	 */
	String serviceName;

	/**
	 * description for documentation
	 */
	String description;

	/**
	 * in case you do not want to specify individual field values but provide a
	 * ready string that is to be provided as request pay-load to the service.
	 * If this is specified, fields ad sheets are not relevant and are ignored
	 */
	String inputJson;
	/**
	 * you can override elements of the JSON or add to it using fields at any
	 * level
	 */
	InputField[] inputFields;

	/**
	 * Specify a qualified attribute to be used to identify a specific item in
	 * the JSON. Fields inside this item are relative to this item
	 */
	InputItem[] inputItems;

	/**
	 * number of errors expected. non-zero implies that we expect this request
	 * to fail, and hence output data expectations are irrelevant.
	 */
	boolean testForFailure;
	/*
	 * output meant for assertions
	 */

	/**
	 * if you want to specify exactly the fields/sheets expected as in the
	 * pay-load format(json). We compare the json elements, and not just a
	 * string comparison to take care of white-space and sequence/order issues
	 */
	String outputJson;
	/**
	 * assertion on fields (with primitive values)
	 */
	OutputField[] outputFields;
	/**
	 * assertions on arrays/lists
	 */
	OutputList[] outputLists;

	/**
	 * assertions on items/objects (which in turn contain fields/lists)
	 */
	OutputItem[] outputItems;

	/**
	 * fields that are used by subsequent test plans
	 */
	ContextField[] fieldsToBeAddedToContext;

	/**
	 * Use java to inspect the result and assert. fully qualified class name
	 * that implements org.simplity.test.Inspector
	 */
	String testClassName;

	/**
	 * how many assertions are you making in this test case? This is meant for reporting purposes
	 */
	int nbrAssertions = 1;
	/**
	 * run this test and report result to the context
	 *
	 * @param ctx
	 * @return error message in case this test fails. null if all OK.
	 */
	public String run(TestContext ctx) {
		String json = this.getInput(ctx);
		Tracer.trace("Input Json : " + json);
		long startedAt = new Date().getTime();
		String msg = null;
		try {
			json = ctx.runService(this.serviceName, json);
			msg = this.assertOutput(json, ctx);
		} catch (Exception e) {
			msg = "Service or serviceTest has a fatal error : "
					+ e.getMessage();
			Tracer.trace(e,
					this.serviceName + " raised fatal error during testing.");
		}
		Tracer.trace("Output JSON : " + json);
		int millis = (int) (new Date().getTime() - startedAt);
		TestResult result = new TestResult(this.serviceName, this.testCaseName,
				millis, msg);
		ctx.addResult(result);
		return msg;

	}

	/**
	 * we carry out all asserts in the output specification. We come out with
	 * the first failure.
	 *
	 * @param output
	 *            json that is returned from service. This is either an array of
	 *            messages or a response json
	 * @return null if it compares well. Error message in case of any trouble
	 */
	String assertOutput(String output, TestContext ctx) {
		JSONObject json = new JSONObject(output);
		boolean succeeded = this.serviceSucceeded(json);
		/*
		 * service succeeded.
		 */
		if (succeeded == false && this.testForFailure == false) {
			return "Service failed while we expected it to succeed.";
		}
		if (this.fieldsToBeAddedToContext != null) {
			for (ContextField field : this.fieldsToBeAddedToContext) {
				field.addToContext(json, ctx);
			}
		}

		if (succeeded && this.testForFailure) {
			return "Service succeeded while we expected it to fail.";
		}

		/*
		 * we are aware that we could be reaching here even after the service
		 * has failed. But if that is what the tester wants us to do...
		 */
		/*
		 * are we expecting a specific json?
		 */
		if (this.outputJson != null) {
			JSONObject expected = new JSONObject(this.outputJson);
			/*
			 * we assert expected attributes, but not bother if there are
			 * others.
			 */
			String msg = expected.agreesWith(json);
			if(msg != null){
				return msg;
			}
		}

		/*
		 * what fields are we expecting?
		 */
		if (this.outputFields != null) {
			for (OutputField field : this.outputFields) {
				String resp = field.match(json, ctx);
				if (resp != null) {
					return resp;
				}
			}
		}

		/*
		 * items
		 */
		if (this.outputItems != null) {
			for (OutputItem item : this.outputItems) {
				String resp = item.match(json, ctx);
				if (resp != null) {
					return resp;
				}
			}
		}

		if (this.outputLists != null) {
			for (OutputList list : this.outputLists) {
				String resp = list.match(json, ctx);
				if (resp != null) {
					return resp;
				}
			}
		}

		/*
		 * last one. Is there a java code?
		 */
		if (this.testClassName != null) {
			String msg = null;
			try {
				Class cls = Class.forName(this.testClassName);
				Object obj = cls.newInstance();
				if (obj instanceof Inspector == false) {
					msg = "Error in test case : " + this.testClassName
							+ " is not an instance of Inspector.";
				} else {
					msg = ((Inspector) obj).test(json, ctx);
				}
			} catch (Exception e) {
				msg = "Error in test case while using class "
						+ this.testClassName + " : " + e.getMessage();
			}
			if (msg != null) {
				return msg;
			}
		}
		/*
		 * fantastic We crossed all hurdles!!!
		 */
		return null;
	}

	/**
	 * @param json
	 * @return
	 */
	private boolean serviceSucceeded(JSONObject json) {
		Object obj = json.opt(ServiceProtocol.REQUEST_STATUS);
		if (obj != null) {
			return ServiceProtocol.STATUS_OK.equals(obj);
		}
		/*
		 * since there is no status indicator, we infer it based on messages
		 */

		obj = json.opt(ServiceProtocol.MESSAGES);
		if (obj == null || obj instanceof JSONArray == false) {
			return true;
		}
		JSONArray msgs = (JSONArray) obj;
		int nbrMsgs = msgs.length();
		for (int i = 0; i < nbrMsgs; i++) {
			obj = msgs.opt(i);
			if (obj instanceof JSONObject) {
				if (((JSONObject) obj).optString("messageType")
						.equals("error")) {
					return false;
				}
			}
		}
		return true;

	}

	/**
	 * validate this components and report any errors
	 *
	 * @param vtx
	 * @return number of errors detected
	 */
	@SuppressWarnings("unused")
	public int validate(ValidationContext vtx) {
		int nbr = 0;
		if (this.inputJson != null) {
			try {
				/*
				 * reason for us to put suppressWarning annotation
				 */
				new JSONObject(this.inputJson);
			} catch (Exception e) {
				vtx.addError(
						"inputPayload is not a valid json\n" + this.inputJson);
				nbr++;
			}
		}
		if (this.outputJson != null) {
			try {
				new JSONObject(this.outputJson);
			} catch (Exception e) {
				vtx.addError(
						"inputPayload is not a valid json\n" + this.outputJson);
				nbr++;
			}
			if (this.outputFields != null || this.outputItems != null
					|| this.outputLists != null || this.testForFailure) {
				vtx.addError(
						"outputJson is specified, and hence other assertions on output are not relevant.");
				nbr++;
			}
			return nbr;
		}
		if (this.outputFields != null) {
			for (OutputField field : this.outputFields) {
				nbr += field.validate(vtx);
			}
		}
		if (this.outputItems != null) {
			for (OutputItem item : this.outputItems) {
				nbr += item.validate(vtx);
			}
		}
		if (this.outputLists != null) {
			for (OutputList list : this.outputLists) {
				nbr += list.validate(vtx);
			}
		}
		if (this.inputFields != null) {
			for (InputField field : this.inputFields) {
				nbr += field.validate(vtx);
			}
		}
		if (this.inputItems != null) {
			for (InputItem item : this.inputItems) {
				nbr += item.validate(vtx);
			}
		}
		return nbr;
	}

	/**
	 * @return
	 */
	String getInput(TestContext ctx) {
		JSONObject json;
		if (this.inputJson == null) {
			json = new JSONObject();
		} else {
			json = new JSONObject(this.inputJson);
		}

		if (this.inputItems != null) {
			for (InputItem item : this.inputItems) {
				item.setInputValues(json, ctx);
			}
		}
		if (this.inputFields != null) {
			for (InputField field : this.inputFields) {
				field.setInputValue(json, ctx);
			}
		}
		return json.toString();
	}

	/**
	 * @return service name
	 */
	public String getServiceName() {
		return this.serviceName;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy