001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.scxml2.io;
018
019import java.io.StringReader;
020
021import org.apache.commons.scxml2.SCXMLExecutor;
022import org.apache.commons.scxml2.SCXMLTestHelper;
023import org.apache.commons.scxml2.model.ModelException;
024import org.apache.commons.scxml2.model.SCXML;
025import org.junit.Test;
026
027import static org.junit.Assert.assertEquals;
028import static org.junit.Assert.assertTrue;
029import static org.junit.Assert.fail;
030
031/**
032 * Test enforcement of required SCXML element attributes, spec http://www.w3.org/TR/2013/WD-scxml-20130801
033 * <p>
034 * TODO required attributes for elements:
035 * <ul>
036 *   <li>&lt;raise&gt; required attribute: 'id'</li>
037 * </ul>
038 * </p>
039 */
040public class SCXMLRequiredAttributesTest {
041
042    private static final String VALID_SCXML =
043            "<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" datamodel=\"jexl\" version=\"1.0\">\n" +
044                    "  <state id=\"s1\">\n" +
045                    "    <transition target=\"fine\">\n" +
046                    "      <if cond=\"true\"><log expr=\"'hello'\"/></if>\n" +
047                    "    </transition>\n" +
048                    "  </state>\n" +
049                    "  <final id=\"fine\"/>\n" +
050                    "</scxml>";
051
052    private static final String SCXML_WITH_MISSING_VERSION =
053            "<scxml xmlns=\"http://www.w3.org/2005/07/scxml\">\n" +
054                    "  <final id=\"fine\"/>\n" +
055                    "</scxml>";
056
057    private static final String SCXML_WITH_INVALID_VERSION =
058            "<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" version=\"2.0\">\n" +
059                    "  <final id=\"fine\"/>\n" +
060                    "</scxml>";
061
062    private static final String SCXML_WITH_MISSING_IF_COND =
063            "<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" version=\"1.0\">\n" +
064                    "  <state id=\"s1\">\n" +
065                    "    <transition target=\"fine\">\n" +
066                    "      <if><log expr=\"'hello'\"/></if>\n" +
067                    "    </transition>\n" +
068                    "  </state>\n" +
069                    "  <final id=\"fine\"/>\n" +
070                    "</scxml>";
071
072    private static final String SCXML_WITH_MISSING_ELSEIF_COND =
073            "<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" version=\"1.0\">\n" +
074                    "  <state id=\"s1\">\n" +
075                    "    <transition target=\"fine\">\n" +
076                    "      <if cond=\"false\"><elseif/><log expr=\"'hello'\"/></if>\n" +
077                    "    </transition>\n" +
078                    "  </state>\n" +
079                    "  <final id=\"fine\"/>\n" +
080                    "</scxml>";
081
082    private static final String SCXML_WITH_MISSING_DATA_ID =
083            "<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" version=\"1.0\">\n" +
084                    "  <datamodel><data></data></datamodel>\n" +
085                    "  <final id=\"fine\"/>\n" +
086                    "</scxml>";
087
088    private static final String SCXML_WITH_MISSING_ASSIGN_LOCATION =
089            "<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" version=\"1.0\">\n" +
090                    "  <state id=\"s1\">\n" +
091                    "    <transition target=\"fine\">\n" +
092                    "      <assign expr=\"1\"/>\n" +
093                    "    </transition>\n" +
094                    "  </state>\n" +
095                    "  <final id=\"fine\"/>\n" +
096                    "</scxml>";
097
098    private static final String SCXML_WITH_MISSING_PARAM_NAME =
099            "<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" version=\"1.0\">\n" +
100                    "  <state id=\"s1\">\n" +
101                    "    <invoke type=\"scxml\" src=\"foo\">\n" + // Note: invalid src, but not executed during test
102                    "      <param expr=\"1\"/>\n" +
103                    "    </invoke>\n" +
104                    "  </state>\n" +
105                    "  <final id=\"fine\"/>\n" +
106                    "</scxml>";
107
108    private static final String SCXML_WITH_PARAM_AND_NAME =
109            "<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" version=\"1.0\">\n" +
110                    "  <state id=\"s1\">\n" +
111                    "    <invoke type=\"scxml\" src=\"foo\">\n" + // Note: invalid src, but not executed during test
112                    "      <param name=\"bar\" expr=\"1\"/>\n" +
113                    "    </invoke>\n" +
114                    "  </state>\n" +
115                    "  <final id=\"fine\"/>\n" +
116                    "</scxml>";
117
118    private static final String SCXML_WITH_MISSING_FOREACH_ARRAY =
119            "<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" version=\"1.0\">\n" +
120                    "  <state id=\"s1\">\n" +
121                    "    <transition target=\"fine\">\n" +
122                    "      <foreach item=\"y\"></foreach>\n" +
123                    "    </transition>\n" +
124                    "  </state>\n" +
125                    "  <final id=\"fine\"/>\n" +
126                    "</scxml>";
127
128    private static final String SCXML_WITH_MISSING_FOREACH_ITEM =
129            "<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" version=\"1.0\">\n" +
130                    "  <state id=\"s1\">\n" +
131                    "    <transition target=\"fine\">\n" +
132                    "      <foreach array=\"[1,2]\"></foreach>\n" +
133                    "    </transition>\n" +
134                    "  </state>\n" +
135                    "  <final id=\"fine\"/>\n" +
136                    "</scxml>";
137
138    private static final String SCXML_WITH_FOREACH =
139            "<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" datamodel=\"jexl\" version=\"1.0\">\n" +
140                    "  <state id=\"s1\">\n" +
141                    "    <transition target=\"fine\">\n" +
142                    "      <foreach array=\"[1,2]\" item=\"x\"></foreach>\n" +
143                    "    </transition>\n" +
144                    "  </state>\n" +
145                    "  <final id=\"fine\"/>\n" +
146                    "</scxml>";
147
148    @Test
149    public void testValidSCXML() throws Exception {
150        SCXML scxml = SCXMLTestHelper.parse(new StringReader(VALID_SCXML), null);
151        SCXMLExecutor exec = SCXMLTestHelper.getExecutor(scxml);
152        exec.go();
153        assertTrue(exec.getStatus().isFinal());
154    }
155
156    @Test
157    public void testSCXMLMissingVersion() throws Exception {
158        try {
159            SCXMLTestHelper.parse(new StringReader(SCXML_WITH_MISSING_VERSION), null);
160            fail("SCXML reading should have failed due to missing version in SCXML");
161        }
162        catch (ModelException e) {
163            assertTrue(e.getMessage().startsWith("<scxml> is missing required attribute \"version\" value"));
164        }
165    }
166
167    @Test
168    public void testSCXMLInvalidVersion() throws Exception {
169        try {
170            SCXMLTestHelper.parse(new StringReader(SCXML_WITH_INVALID_VERSION), null);
171            fail("SCXML reading should have failed due to missing version in SCXML");
172        }
173        catch (ModelException e) {
174            assertEquals("The <scxml> element defines an unsupported version \"2.0\", only version \"1.0\" is supported.", e.getMessage());
175        }
176    }
177
178    @Test
179    public void testSCXMLMissingIfCond() throws Exception {
180        try {
181            SCXMLTestHelper.parse(new StringReader(SCXML_WITH_MISSING_IF_COND), null);
182            fail("SCXML reading should have failed due to missing if condition in SCXML");
183        }
184        catch (ModelException e) {
185            assertTrue(e.getMessage().startsWith("<if> is missing required attribute \"cond\" value"));
186        }
187    }
188
189    @Test
190    public void testSCXMLMissingElseIfCond() throws Exception {
191        try {
192            SCXMLTestHelper.parse(new StringReader(SCXML_WITH_MISSING_ELSEIF_COND), null);
193            fail("SCXML reading should have failed due to missing elseif condition in SCXML");
194        }
195        catch (ModelException e) {
196            assertTrue(e.getMessage().startsWith("<elseif> is missing required attribute \"cond\" value"));
197        }
198    }
199
200    @Test
201    public void testSCXMLMissingDataId() throws Exception {
202        try {
203            SCXMLTestHelper.parse(new StringReader(SCXML_WITH_MISSING_DATA_ID), null);
204            fail("SCXML reading should have failed due to missing data id in SCXML");
205        }
206        catch (ModelException e) {
207            assertTrue(e.getMessage().startsWith("<data> is missing required attribute \"id\" value"));
208        }
209    }
210
211    @Test
212    public void testSCXMLMissingAssignLocation() throws Exception {
213        try {
214            SCXMLTestHelper.parse(new StringReader(SCXML_WITH_MISSING_ASSIGN_LOCATION), null);
215            fail("SCXML reading should have failed due to missing assign location in SCXML");
216        }
217        catch (ModelException e) {
218            assertTrue(e.getMessage().startsWith("<assign> is missing required attribute \"location\" value"));
219        }
220    }
221
222    @Test
223    public void testSCXMLMissingParamName() throws Exception {
224        try {
225            SCXMLTestHelper.parse(new StringReader(SCXML_WITH_MISSING_PARAM_NAME), null);
226            fail("SCXML reading should have failed due to missing param name in SCXML");
227        }
228        catch (ModelException e) {
229            assertTrue(e.getMessage().startsWith("<param> is missing required attribute \"name\" value"));
230        }
231    }
232
233    @Test
234    public void testSCXMLParamWithName() throws Exception {
235        SCXMLTestHelper.parse(new StringReader(SCXML_WITH_PARAM_AND_NAME), null);
236        // Note: cannot execute this instance without providing proper <invoke> src attribute
237    }
238
239    @Test
240    public void testSCXMLMissingForeachArray() throws Exception {
241        try {
242            SCXMLTestHelper.parse(new StringReader(SCXML_WITH_MISSING_FOREACH_ARRAY), null);
243            fail("SCXML reading should have failed due to missing foreach array in SCXML");
244        }
245        catch (ModelException e) {
246            assertTrue(e.getMessage().startsWith("<foreach> is missing required attribute \"array\" value"));
247        }
248    }
249
250    @Test
251    public void testSCXMLMissingForeachItem() throws Exception {
252        try {
253            SCXMLTestHelper.parse(new StringReader(SCXML_WITH_MISSING_FOREACH_ITEM), null);
254            fail("SCXML reading should have failed due to missing foreach item in SCXML");
255        }
256        catch (ModelException e) {
257            assertTrue(e.getMessage().startsWith("<foreach> is missing required attribute \"item\" value"));
258        }
259    }
260
261    @Test
262    public void testSCXMLWithForEach() throws Exception {
263        SCXML scxml = SCXMLTestHelper.parse(new StringReader(SCXML_WITH_FOREACH), null);
264        SCXMLExecutor exec = SCXMLTestHelper.getExecutor(scxml);
265        exec.go();
266        assertTrue(exec.getStatus().isFinal());
267    }
268}