001    /*
002    Copyright (c) 1996-2011, Damon Hart-Davis
003    All rights reserved.
004    
005    Redistribution and use in source and binary forms, with or without
006    modification, are permitted provided that the following conditions are
007    met:
008    
009      * Redistributions of source code must retain the above copyright
010        notice, this list of conditions and the following disclaimer.
011    
012      * Redistributions in binary form must reproduce the above copyright
013        notice, this list of conditions and the following disclaimer in the
014        documentation and/or other materials provided with the
015        distribution.
016    
017    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018    IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028    */
029    
030    /*
031     * Created by IntelliJ IDEA.
032     * User: Administrator
033     * Date: 28-Dec-02
034     * Time: 22:24:51
035     */
036    package org.hd.d.pg2k.test.dev;
037    
038    import java.io.ByteArrayInputStream;
039    import java.io.ByteArrayOutputStream;
040    import java.io.DataInputStream;
041    import java.io.EOFException;
042    import java.io.IOException;
043    import java.io.InputStream;
044    import java.util.ArrayList;
045    import java.util.Arrays;
046    import java.util.HashMap;
047    import java.util.Map;
048    import java.util.Random;
049    import java.util.SortedMap;
050    import java.util.zip.ZipEntry;
051    import java.util.zip.ZipInputStream;
052    import java.util.zip.ZipOutputStream;
053    
054    import junit.framework.TestCase;
055    
056    import org.hd.d.pg2k.svrCore.AllExhibitProperties;
057    import org.hd.d.pg2k.svrCore.CoreConsts;
058    import org.hd.d.pg2k.svrCore.ExhibitAttrUtils;
059    import org.hd.d.pg2k.svrCore.FileTools;
060    import org.hd.d.pg2k.svrCore.FileTools.ZE;
061    import org.hd.d.pg2k.svrCore.GenUtils;
062    import org.hd.d.pg2k.svrCore.HostUtils;
063    import org.hd.d.pg2k.svrCore.ROByteArray;
064    import org.hd.d.pg2k.svrCore.Tuple.Pair;
065    import org.hd.d.pg2k.svrCore.datasource.ExhibitDataFileSource;
066    import org.hd.d.pg2k.svrCore.location.GeoUtils.CCTLD;
067    import org.hd.d.pg2k.svrCore.props.GenProps;
068    import org.hd.d.pg2k.svrCore.props.LocalProps;
069    import org.hd.d.pg2k.webSvr.ads.AdUtils;
070    import org.hd.d.pg2k.webSvr.catalogue.TrailData;
071    
072    /**Simple tests of access/handling of local and other configuration data.
073     */
074    public final class ConfigFilesTest extends TestCase
075        {
076        public ConfigFilesTest(final String name)
077            {
078            super(name);
079            }
080    
081        /**Test that we can find some of the principal files.
082         * We may also check that we can parse/use them.
083         * <p>
084         * We skip these tests if we do not have full/normal access
085         * to the filesystem.
086         */
087        public void testConfigFileAccess()
088            {
089            if(!Main.isAccessToFilesystem())
090                {
091                System.err.println("No access to filesystem: skipping tests: " + getName());
092                return;
093                }
094    
095            // Check that local properties are loaded.
096            assertTrue("Must have access to LocalProps",
097                       LocalProps.getTimestamp() > 0);
098    
099            // Ensure that we can find key global properties
100            // such as the set of exhibit attribute/discardable words....
101            assertTrue("Must be able to load exhibit attribute words from global properties",
102                       ExhibitAttrUtils.getAttrWords().getAttrWords().length > 0);
103    //        // ...and the built-in location database.
104    //        assertTrue("Must be able to load the built-in location DB",
105    //                   !LocationMap.getBuiltInLocationMap().isEmpty());
106    
107            // Check that we've correctly loaded the LocalProps
108            // by validating a specific value or two.
109            assertEquals("Could not retrieve correct server-slowdown factor from LocalProps",
110                         2, LocalProps.getServerSlowdownFactor());
111            assertTrue("Could not retrieve correct low-CPU threshold factor from LocalProps",
112                    Math.abs(0.5f - LocalProps.getLightLoadMax()) < 0.0001f);
113            assertTrue("Could not retrieve correct high-CPU threshold factor from LocalProps",
114                    Math.abs(3.0f - LocalProps.getHeavyLoadMin()) < 0.0001f);
115            }
116    
117        /**Test access to hostname matching in GenProps.
118         * This checks for some data that we know to be there.
119         */
120        public void testGPHostMatching()
121            throws Exception
122            {
123            if(!Main.isAccessToFilesystem())
124                {
125                System.err.println("No access to filesystem: skipping tests: " + getName());
126                return;
127                }
128    
129            // Check that we can load test data and that it is non-empty.
130            final ExhibitDataFileSource efds = new ExhibitDataFileSource();
131            final GenProps gp = efds.getGenProps(-1);
132            assertNotNull("Must not retrieve null GenProps", gp);
133            assertTrue("Must not retrieve empty GenProps",
134                       gp.timestamp != 0);
135    
136            // Check that we have hotlink-allow host lists working...
137            assertNotNull("We expect the allow-hotlink hosts list to be non-null", gp.getHotLinkAllowHosts());
138            assertFalse("We expect the allow-hotlink hosts list to be non-empty", gp.getHotLinkAllowHosts().isEmpty());
139            assertEquals("We expect the allow-hotlink hosts list to have right length after conflating normalised entries", 3, gp.getHotLinkAllowHosts().size());
140            assertNull("We expect the allow-hotlink regex pattern to be null", gp.getHotLinkAllowHostsRegex());
141    
142            // Check that we have hotlink-disallow host lists working...
143            assertNotNull("We expect the disallow-hotlink hosts list to be non-null", gp.getHotLinkDisallowHosts());
144            assertFalse("We expect the disallow-hotlink hosts list to be non-empty", gp.getHotLinkDisallowHosts().isEmpty());
145            assertEquals("We expect the disallow-hotlink hosts list to have right length after conflating normalised entries", 4, gp.getHotLinkDisallowHosts().size());
146            assertNotNull("We expect the disallow-hotlink regex pattern to be non-null", gp.getHotLinkDisallowHostsRegex());
147    
148            // Check that one paticular set of cases is covered by the regex.
149            assertTrue("Must be able to match simple disallow case", gp.getHotLinkDisallowHostsRegex().matcher("profile.myspace.com").matches());
150            assertTrue("Must be able to match arbitrary disallow case", gp.getHotLinkDisallowHostsRegex().matcher("new.subDOMAIN-MixEd-caSE9.myspace.COM").matches());
151    
152    
153    //TODO
154    
155            }
156    
157        /**Test access to author database in GenProps.
158         * This checks for some data that we know to be there.
159         */
160        public void testAuthDB()
161            throws Exception
162            {
163            if(!Main.isAccessToFilesystem())
164                {
165                System.err.println("No access to filesystem: skipping tests: " + getName());
166                return;
167                }
168    
169            // Check that we have a standard regex match for disallow hosts working.
170    
171            // Check that we can load test data and that it is non-empty.
172            final ExhibitDataFileSource efds = new ExhibitDataFileSource();
173            final GenProps gp = efds.getGenProps(-1);
174            assertNotNull("Must not retrieve null GenProps", gp);
175            assertTrue("Must not retrieve empty GenProps",
176                       gp.timestamp != 0);
177    
178            final GenProps.AuthData adDHD = gp.getAuthData("DHD");
179            assertNotNull("Cannot retrieve author data for DHD", adDHD);
180            assertEquals("Author initials wrong for DHD",
181                         "DHD", adDHD.auth);
182            assertEquals("Author name is wrong for DHD",
183                         "Damon Hart-Davis", adDHD.name);
184            assertEquals("Author home-page URL is wrong for DHD",
185                         "http://d.hd.org/", adDHD.www);
186            assertEquals("Author email is wrong for DHD",
187                         "d@hd.org", adDHD.email);
188            assertEquals("Author description is wrong for DHD",
189                         "Gallery maintainer.", adDHD.desc);
190    
191            // Check that this seems to survive (de)serialisation...
192            final GenProps sgp = (GenProps)
193                SerializationTest.checkObjectCanBeSerialisedAndDeserialised(gp);
194            final GenProps.AuthData adDHD2 = sgp.getAuthData("DHD");
195            assertNotNull("Cannot retrieve author data for DHD after (de)serialisation", adDHD2);
196            assertEquals("Author initials wrong for DHD after (de)serialisation",
197                         "DHD", adDHD2.auth);
198            assertEquals("Author name is wrong for DHD after (de)serialisation",
199                         "Damon Hart-Davis", adDHD2.name);
200            assertEquals("Author home-page URL is wrong for DHD after (de)serialisation",
201                         "http://d.hd.org/", adDHD2.www);
202            assertEquals("Author email is wrong for DHD after (de)serialisation",
203                         "d@hd.org", adDHD2.email);
204            assertEquals("Author description is wrong for DHD after (de)serialisation",
205                         "Gallery maintainer.", adDHD2.desc);
206    
207            assertTrue("Must contain expected hotlink-allowed host",
208                       gp.getHotLinkAllowHosts().contains(HostUtils.normaliseVirtualHostName("www.hd.org")));
209            assertTrue("Must contain expected hotlink-disallowed host",
210                       gp.getHotLinkDisallowHosts().contains(HostUtils.normaliseVirtualHostName("bad.host1")));
211    
212            // Check that equality works too.
213            assertEquals("AuthData should compare equals after (de)serialisation",
214                         adDHD, adDHD2);
215            }
216    
217        /**Test access to generic key/value pairs in GenProps.
218         * This checks for some data that we know to be there.
219         */
220        public void testGen()
221            throws Exception
222            {
223            if(!Main.isAccessToFilesystem())
224                {
225                System.err.println("No access to filesystem: skipping tests: " + getName());
226                return;
227                }
228    
229            // Check that we can load test data and that it is non-empty.
230            final ExhibitDataFileSource efds = new ExhibitDataFileSource();
231            final GenProps gp = efds.getGenProps(-1);
232            assertNotNull("Must not retrieve null GenProps", gp);
233            assertTrue("Must not retrieve empty GenProps",
234                       gp.timestamp != 0);
235    
236            // Check that generic map is not null.
237            final Map<String,String> gen = gp.getGen();
238            assertNotNull("Gen map must not be null", gen);
239    
240            if(gen.isEmpty())
241                {
242                System.err.println("gen = " + (new ArrayList<Map.Entry<String,String>>(gen.entrySet())));
243                fail("Gen map must not be empty (should contain test value(s))");
244                }
245    
246            // Check that the generic map is not (trivially) mutable.
247            try
248                {
249                gen.put("a", "b");
250                fail("Gen map is apparently mutable");
251                }
252            catch(final UnsupportedOperationException e) { /* Good; rejected change. */ }
253    
254            // Get generic test value...
255            final String testKey = "testGen.key";
256            final String testValue = "testGenValue";
257            assertEquals("Must be able to retrieve expected test value", testValue, gen.get(testKey));
258    
259            // Look for ECPM value...
260            assertEquals("Must be able to look up ECPM value", 10, AdUtils.computeECPM(gp, "kPMPILV=MRTF"));
261    
262            // Check for "sensitive" names.
263            assertTrue("Must be able to detect sensitive names correctly [1]", GenUtils.isSensitive("a/an-obituary-ANON.tif", gp));
264            assertTrue("Must be able to detect sensitive names correctly [2]", GenUtils.isSensitive("a/some-death-notice-mono-ANON.gif", gp));
265            assertFalse("Must be able to detect sensitive names correctly [3]", GenUtils.isSensitive("a/my-cat-ANON.gif", gp));
266    
267            // Check that this seems to survive (de)serialisation...
268            final GenProps sgp = (GenProps)
269                SerializationTest.checkObjectCanBeSerialisedAndDeserialised(gp);
270            final Map<String,String> gen2 = sgp.getGen();
271            assertEquals("Gen maps must be equal after (de)serialisation", gen, gen2);
272            assertEquals("Must be able to retrieve expected test value after (de)serialisation", testValue, gen2.get(testKey));
273            assertEquals("Must be able to look up ECPM value after (de)serialisation", 10, AdUtils.computeECPM(sgp, "kPMPILV=MRTF"));
274            }
275    
276        /**Ensure that we can load the exhibits and meta data from the filesystem.
277         * This uses the ExhibitDataFileSource.
278         * <p>
279         * We skip these tests if we do not have full/normal access
280         * to the filesystem.
281         */
282        public void testLoadOfExhibitData()
283            throws Exception
284            {
285            if(!Main.isAccessToFilesystem())
286                {
287                System.err.println("No access to filesystem: skipping tests: " + getName());
288                return;
289                }
290    
291            // Check that we can load test data and that it is non-empty.
292            final ExhibitDataFileSource efds = new ExhibitDataFileSource();
293            final GenProps gp = efds.getGenProps(-1);
294            assertNotNull("Must not retrieve null GenProps", gp);
295            assertTrue("Must not retrieve empty GenProps",
296                       gp.timestamp != 0);
297            final AllExhibitProperties aep =
298                efds.getAllExhibitProperties(-1);
299            assertNotNull("Must not retrieve null AllExhibitProperties", aep);
300            assertTrue("Must not retrieve empty AllExhibitProperties",
301                       aep.aeid.size() > 0);
302            assertTrue("Must retrieve non-empty global-immutable properties",
303                       aep.epgi.longHash != 0);
304    //        assertTrue("Must retrieve non-empty LocationMap",
305    //                   aep.lm.hashCode() != 0);
306            }
307    
308        /**Test classified ad filtering. */
309        public static void testClassifiedAdFiltering()
310            {
311            // Test country filtering...
312            final GenProps.ClassifiedAd cladNoCountries = new GenProps.ClassifiedAd(
313                    "hello", ".", 0, 0, null, null);
314            assertFalse("With no countries specified, anything (even null) must be acceptable",
315                    cladNoCountries.wrongCountry(null));
316            assertFalse("With no countries specified, any valid ccTLD must be acceptable",
317                    cladNoCountries.wrongCountry(new CCTLD("uk")));
318            final GenProps.ClassifiedAd cladUK = new GenProps.ClassifiedAd(
319                    "hello", ".", 0, 0, null, "uk");
320            assertTrue("With one-or-more countries specified, null is not acceptable",
321                    cladUK.wrongCountry(null));
322            assertFalse("With one-or-more countries specified, a matching ccTLD must be acceptable",
323                    cladUK.wrongCountry(new CCTLD("uk")));
324            }
325    
326        /**Test trail data parsing. */
327        public static void testTrailDataParsing()
328            throws Exception
329            {
330            // Unusable trail data texts that should be rejected.
331            final String badData[] =
332                {
333                "" /* Bad empty file. */ ,
334                "blah" /* Missing title. */ ,
335                "@ \n" /* Missing title. */ ,
336                "@ Title" /* Missing body. */ ,
337                "@ Title\n" /* Missing body. */ ,
338                "@ Title\n\n" /* Missing body. */ ,
339                };
340            // Check that seriously bad trail texts are rejected outright.
341            for(final String b : badData)
342                {
343                try
344                    {
345                    _parseTrailTestAsString(b);
346                    fail("TrailData.parseFromByteStream() failed to reject bad input text");
347                    }
348                catch(final IllegalArgumentException e) { /* Good, correctly rejected bad text. */ }
349                catch(final EOFException e) { /* Good, correctly rejected bad text. */ }
350                }
351    
352            // Minimal trail text for simple parsing.
353            final String trailText1 =
354                "@  Test  title 1 \n" +
355                "    body   text   ";
356            final TrailData td1 = _parseTrailTestAsString(trailText1);
357            assertEquals("title should have ends trimmed of whitespace but be otherwise unmolested",
358                    "Test  title 1", td1.title.toString());
359            assertEquals("there should be one body entry",
360                    1, td1.body.size());
361            assertEquals("body text should have ends trimmed of whitespace but be otherwise unmolested",
362                    "body   text", td1.body.get(0).second.toString());
363    
364            // More complex trail text for simple parsing.
365            final String trailText2 =
366                "@ Title 2\r\n" +
367                "Intro text\nwaffles on a little...\n" +
368                "* example-ANON.jpg  Comment\r\nText for first exhibit.\n\n\n\n\n" +
369                "* cat-ANON.gif          Catty comment\nText for cat exhibit.\n\n" +
370                "* -2- Another catty comment\nText for next cat exhibit.\nMore text...\n" +
371                "\n\nFinal trailing\r\npara...";
372            final TrailData td2 = _parseTrailTestAsString(trailText2);
373            assertEquals("title should have ends trimmed of whitespace but be otherwise unmolested",
374                    "Title 2", td2.title.toString());
375            assertEquals("there should be five body entries",
376                    5, td2.body.size());
377            // Intro para...
378            assertEquals("body text should have ends trimmed of whitespace but be otherwise unmolested",
379                    "Intro text waffles on a little...", td2.body.get(0).second.toString());
380            // First exhibit...
381            assertEquals("exhibit name should be correctly captured",
382                    "example-ANON.jpg", td2.body.get(1).first.toString());
383            assertEquals("body text should have ends trimmed of whitespace but be otherwise unmolested",
384                    "Text for first exhibit.", td2.body.get(1).second.toString());
385            // Second exhibit...
386            assertEquals("exhibit name should be correctly captured",
387                    "cat-ANON.gif", td2.body.get(2).first.toString());
388            assertEquals("body text should have ends trimmed of whitespace but be otherwise unmolested",
389                    "Text for cat exhibit.", td2.body.get(2).second.toString());
390            // Third exhibit...
391            assertEquals("exhibit name should be correctly synthesised",
392                    "cat-2-ANON.gif", td2.body.get(3).first.toString());
393            assertEquals("body text should have ends trimmed of whitespace but be otherwise unmolested",
394                    "Text for next cat exhibit. More text...", td2.body.get(3).second.toString());
395            // Final para...
396            assertNull("exhibit name should be correctly synthesised",
397                    td2.body.get(4).first);
398            assertEquals("body text should have ends trimmed of whitespace but be otherwise unmolested",
399                    "Final trailing para...", td2.body.get(4).second.toString());
400            }
401    
402        /**Parse trail text supplied as (7-bit ASCII) String; never null.
403         * @param trailText  7-bit-ASCII trail text; never null
404         */
405        private static TrailData _parseTrailTestAsString(final String trailText)
406            throws IOException
407            {
408            return TrailData.parseFromByteStream(new ByteArrayInputStream(trailText.getBytes(CoreConsts.FILE_ENCODING_ASCII7)));
409            }
410    
411        /**Test handling of ZIP-file data in/for exhibits.
412         */
413        public static void testZIPHandling()
414            throws Exception
415            {
416            // Create a simple ZIP with a few random files in it. */
417            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
418            final ZipOutputStream zos = new ZipOutputStream(baos);
419            final int nEntries = 1 + rnd.nextInt(5); // Cannot be empty...
420            final Map<String,ROByteArray> entryContent = new HashMap<String, ROByteArray>(nEntries);
421            for(int i = 0; i < nEntries; ++i)
422                {
423                final String name = "file" + (i+1);
424                final byte[] data = new byte[rnd.nextInt(1024)];
425                rnd.nextBytes(data);
426                final ROByteArray content = new ROByteArray(data);
427                entryContent.put(name, content);
428                // Randomly set level and method to stress things...
429                zos.setLevel(rnd.nextInt(10));
430                final ZipEntry ze = new ZipEntry(name);
431                if(0 == (i&1)) { ze.setSize(data.length); } // Set size on alternate entries only.
432                zos.putNextEntry(ze);
433                zos.write(data);
434                zos.closeEntry();
435                }
436            zos.finish();
437            zos.close();
438    
439            // Now make sure that we can extract the file listing and content...
440            final FileTools.RandomAccessData rad = FileTools.wrapBytesAsRandomAccessData(baos.toByteArray());
441            // Check that all entries are visible...
442            final SortedMap<CharSequence, ZE> centralDir = FileTools.getZIPEntriesLengthAndOffset(rad);
443            assertNotNull("Must be able to find central directory", centralDir);
444            assertEquals("Same file count must exist in central directory as stored", entryContent.size(), centralDir.size());
445            // Check individual entries...
446            for(final String e : entryContent.keySet())
447                {
448                assertTrue("Each file must exist in central directory as recovered", centralDir.containsKey(e));
449    
450                // Test the stream-based extractor...
451                // It needs to be set up afresh each time (and is otherwise inefficient).
452                final Pair<ZipEntry, ROByteArray> entryS = FileTools.getZIPEntry(new ByteArrayInputStream(baos.toByteArray()), e);
453                assertNotNull("Each entry must be visible with the stream-based extractor", entryS);
454                assertEquals("Each entry's name should be preserved with the stream-based extractor", e, entryS.first.getName());
455                assertEquals("Each entry's data should be preserved with the stream-based extractor", entryContent.get(e), entryS.second);
456    
457                // Check that the offset in the directory is correct.
458                final int offset = centralDir.get(e).offset;
459                assertTrue("offset cannot be negative", offset >= 0);
460                assertTrue("offset cannot be beyond EOF", offset < baos.size());
461                assertTrue("offset cannot be beyond start of end header", offset < baos.size() - ZipEntry.ENDHDR);
462                final InputStream is = new ByteArrayInputStream(baos.toByteArray());
463                is.skip(offset);
464                final ZipInputStream zis = new ZipInputStream(is);
465                final DataInputStream dis = new DataInputStream(zis);
466                final ZipEntry ze = zis.getNextEntry();
467                assertEquals("Must locate correct ZIP entry with offset", e, ze.getName());
468                // Verify that data/content was preserved...
469                final ROByteArray ro = entryContent.get(ze.getName());
470                final byte[] tmp = new byte[ro.length()];
471                dis.readFully(tmp);
472                assertTrue(Arrays.equals(tmp, ro.toByteArray()));
473                zis.close(); // Release resources.
474                }
475            }
476    
477        /**Test checking of power-state.
478         * Mainly to ensure that nothing crashes nor hangs.
479         */
480        public static void testPowerStateChecking()
481            {
482            GenUtils.mustConservePower();
483            }
484    
485    
486        /**OK pseudo-random source. */
487        private static final Random rnd = new Random();
488        }