"""
Tests to do with new-style data modelling and VO-DML serialisation.
"""

#c Copyright 2008-2025, the GAVO project <gavo@ari.uni-heidelberg.de>
#c
#c This program is free software, covered by the GNU GPL.  See the
#c COPYING file in the source distribution.


from gavo.helpers import testhelpers

from io import StringIO
import re

from gavo import base
from gavo import dm
from gavo import formats
from gavo import rsc
from gavo import rscdef
from gavo import rscdesc
from gavo import svcs
from gavo.dm import dmrd
from gavo.dm import sil
from gavo.helpers import testtricks
from gavo.formats import votablewrite


def normalizeSIL(sil):
	return re.sub(r"\s+", " ", sil).strip()


INLINE_MODEL = """
<vo-dml:model xmlns:vo-dml="http://www.ivoa.net/xml/VODML/v1">
  <name>localinline</name>
  <title>Local inline DM</title>
  <version>1.0</version>
  <lastModified>2016-07-14Z11:17:00</lastModified>

  <objectType>
  	<vodml-id>Polar</vodml-id>
  	<name>Polar</name>
  	<attribute>
  		<vodml-id>Polar.rule</vodml-id>
  		<name>rule</name>
  		<datatype>
  			<vodml-ref>dachstoy:Cooler</vodml-ref>
  		</datatype>
  	</attribute>
  </objectType>
</vo-dml:model>
"""


class XSDTest(testhelpers.VerboseTest, testtricks.XSDTestMixin):
	def testCanSchemaValidate(self):
		with open(base.getPathForDistFile("dm/meas.vo-dml.xml"), "rb") as f:
			self.assertValidates(f.read())


class _InlineModel(testhelpers.TestResource):
	def make(self, ignored):
		return dm.Model.fromFile(StringIO(INLINE_MODEL))

_inlineModel = _InlineModel()


class ModelTest(testhelpers.VerboseTest):
	resources = [("inlineModel", _inlineModel)]

	def testMetadataParsing(self):
		toydm = dm.getModelForPrefix("dachstoy")
		self.assertEqual(toydm.description,
			"A toy model for DaCHS regression testing")
		self.assertEqual(toydm.title, "DaCHS Toy model")
		self.assertEqual(toydm.version, "1.0a-pl23.44c")
		self.assertEqual(toydm.uri, "http://docs.g-vo.org/dachstoy")
	
	def testIdAccess(self):
		toydm = dm.getModelForPrefix("dachstoy")
		res = toydm.getByVODMLId("Ruler.width")
		self.assertEqual(res.find("description").text, "A dimension")

	def testPrefixIgnored(self):
		toydm = dm.getModelForPrefix("dachstoy")
		res = toydm.getByVODMLId("dachstoy:Ruler.width")
		self.assertEqual(res.find("description").text, "A dimension")

	def testNoIdAccess(self):
		toydm = dm.getModelForPrefix("dachstoy")
		self.assertRaisesWithMsg(base.NotFoundError,
			"data model element 'Broken.toy' could not be located"
			" in dachstoy data model",
			toydm.getByVODMLId,
			("Broken.toy",))
	
	def testGlobalResolution(self):
		res = dm.resolveVODMLId("dachstoy:Ruler.width")
		self.assertEqual(res.find("description").text, "A dimension")

	def testLoadFromFile(self):
		att = self.inlineModel.getByVODMLId("localinline:Polar.rule")
		self.assertEqual(att.find("datatype/vodml-ref").text, "dachstoy:Cooler")
		self.assertEqual(self.inlineModel.prefix, "localinline")

	def testUnknownPrefix(self):
		# this standin DM behaviour should change to an exception as this matures
		model = dm.getModelForPrefix("notexisting")
		self.assertEqual(model.title, "DaCHS standin model")
		self.assertEqual(model.uri, "urn:dachsjunk:not-model:notexisting")


class AttributeResolutionTest(testhelpers.VerboseTest):
	maxDiff = None

	def testDirect(self):
		attDef = dm.getAttributeDefinition("meas:Asymmetrical3D", "minus")
		self.assertEqual(attDef, {
				'datatype': 'ivoa:RealQuantity',
				'description': 'Extent in each negative axis direction.',
				'vodml-id': 'Asymmetrical3D.minus'})
	
	def testIndirect(self):
		attDef = dm.getAttributeDefinition("meas:GenericMeasure", "ucd")
		self.assertEqual(attDef["datatype"], "ivoa:string")
		self.assertEqual(attDef["vodml-id"], "Measure.ucd")

	def testComposition(self):
		attDef = dm.getAttributeDefinition("coords:CoordSpace", "axis")
		self.assertEqual(attDef, {
			'vodml-id': 'CoordSpace.axis',
			'description': 'Describes an axis of the coordinate space.',
			'datatype': 'coords:Axis'})


class SILGrammarTest(testhelpers.VerboseTest):
	def testPlainObject(self):
		res = sil.getGrammar().parseString("""
			(:testclass) {
				attr1: plain12-14
				attr2: "this is a ""weird"" literal"
			}""")
		self.assertEqual(res[0],
			('obj', ':testclass', [
				('attr', 'attr1', 'plain12-14'),
				('attr', 'attr2', 'this is a "weird" literal')]))
	
	def testNestedObject(self):
		res = sil.getGrammar().parseString("""
			(:testclass) {
				attr1: (:otherclass) {
						attr2: val
					}
			}""")
		self.assertEqual(res[0],
			('obj', ':testclass', [
				('attr', 'attr1',
					('obj', ':otherclass', [
						('attr', 'attr2', 'val')]))]))

	def testObjectCollection(self):
		res = sil.getGrammar().parseString("""
			(:testclass) {
				seq: (:otherclass)[
					{attr1: a}
					{attr1: b}
					{attr1: c}]}""")
		self.assertEqual(res[0],
			('obj', ':testclass', [
				('attr', 'seq',
					('coll', ':otherclass', [
						('obj', None, [('attr', 'attr1', 'a')]),
						('obj', None, [('attr', 'attr1', 'b')]),
						('obj', None, [('attr', 'attr1', 'c')]),]))]))

	def testImmediateCollection(self):
		res = sil.getGrammar().parseString("""
			(:testclass) {
				seq: [a "b c d" @e]}""")
		self.assertEqual(res[0],
			('obj', ':testclass',
				[('attr', 'seq',
					('coll', None, ['a', 'b c d', 'e']))]))

	def testAtomicFallback(self):
		res = sil.getGrammar().parseString("""
			(:testclass) {
				attr1: val | fallback
			}""")
		self.assertEqual(res[0],
			('obj', ':testclass', [
				('attr', 'attr1',
					('fallback', 'val', 'fallback'))]))
						

	def testObjectFallback(self):
		res = sil.getGrammar().parseString("""
			(:testclass) {
				attr1: (:otherclass) {
						attr2: val
					}
					| (:stillotherclass) { attr3: noval}
			}""")
		self.assertEqual(res[0],
			('obj', ':testclass', [
				('attr', 'attr1',
					('fallback',
						('obj', ':otherclass', [
							('attr', 'attr2', 'val')]),
						('obj', ":stillotherclass", [
							('attr', 'attr3', 'noval')])))]))

	def testNullLiteral(self):
		res = sil.getGrammar().parseString("""
			(:testclass) {
				seq: [a __NULL__ @e]
				val: __NULL__}""")
		self.assertEqual(res[0],
			('obj', ':testclass', [
				('attr', 'seq',
					('coll', None, ['a', None, 'e'])),
				('attr', 'val', None)]))


class SILParserTest(testhelpers.VerboseTest):
	def testNestedObject(self):
		res = sil.getAnnotation("""
			(testdm:testclass) {
				attr1: (testdm:otherclass) {
						attr2: val
					}
			}""", dmrd.getAnnotationMaker(None))
		self.assertEqual(normalizeSIL(res.asSIL()),
			'(testdm:testclass) { (testdm:otherclass) { attr2: val} }')

	def testObjectCollection(self):
		res = sil.getAnnotation("""
			(testdm:testclass) {
				seq: (testdm:otherclass)[
					{attr1: a}
					{attr1: b}
					{attr1: c}]}""", dmrd.getAnnotationMaker(None))
		self.assertEqual(normalizeSIL(res.asSIL()),
			'(testdm:testclass) { seq: (testdm:otherclass)'
			' [{ attr1: a} { attr1: b} { attr1: c} ] }')

	def testAtomicCollection(self):
		res = sil.getAnnotation("""
			(testdm:testclass) {
				seq: [a "b c" 3.2]}""", dmrd.getAnnotationMaker(None))
		self.assertEqual(normalizeSIL(res.asSIL()),
			'(testdm:testclass) { seq: [a "b c" 3.2] }')

	def testComments(self):
		res = sil.getAnnotation("""/* comment with stuff */
			(testdm:testclass) /* another comment */ { /* and yet one */
				seq: [a "b c" 3.2] /* here's an additional comment */}
				/* final comment */""", dmrd.getAnnotationMaker(None))
		self.assertEqual(normalizeSIL(res.asSIL()),
			'(testdm:testclass) { seq: [a "b c" 3.2] }')

	def testNoUntypedRoot(self):
		self.assertRaisesWithMsg(base.StructureError,
			"Root of Data Model annotation must have a type.",
			sil.getAnnotation,
			("{attr1: (testdm:otherclass) {attr2: val}}",
				dmrd.getAnnotationMaker(None)))

	def testWithoutType(self):
		res = sil.getAnnotation("(testdm:testclass){attr1: {attr2: val}}",
			dmrd.getAnnotationMaker(None))
		self.assertEqual(normalizeSIL(res.asSIL()),
			'(testdm:testclass) { { attr2: val} }')
	
	def testWithFallback(self):
		res = sil.getAnnotation("(testdm:testclass){attr1: foo | bar}",
			dmrd.getAnnotationMaker(None))
		self.assertEqual(normalizeSIL(res.asSIL()),
			'(testdm:testclass) { attr1: foo}')


class AnnotationTest(testhelpers.VerboseTest):
	def testAtomicValue(self):
		t = base.parseFromString(rscdef.TableDef,
			"""<table id="foo">
				<dm>
					(testdm:testclass) {
						attr1: test
					}
				</dm></table>""")
		self.assertEqual(t.annotations[0].type, "testdm:testclass")
		self.assertEqual(t.annotations[0].childRoles["attr1"].value,
			"test")
		self.assertEqual(t.annotations[0].childRoles["attr1"].instance(),
			t.annotations[0])

	def testAtomicFallback(self):
		t = base.parseFromString(rscdef.TableDef,
			"""<table id="foo">
				<dm>
					(testdm:testclass) {
						attr1: @nix | test
					}
				</dm>
				</table>""")
		self.assertEqual(t.annotations[0].type, "testdm:testclass")
		self.assertEqual(t.annotations[0].childRoles["attr1"].value,
			"test")
		self.assertEqual(t.annotations[0].childRoles["attr1"].instance(),
			t.annotations[0])

	def testObjectFallback(self):
		t = base.parseFromString(rscdef.TableDef,
			"""<table id="foo">
				<dm>
					(testdm:testclass) {
						attr1: {
							bla: @nix
						}
						| {
							kaputt: true
						}
					}
				</dm>
				</table>""")
		self.assertEqual(t.annotations[0].type, "testdm:testclass")
		self.assertEqual(t.annotations[0].childRoles["attr1"]
			.childRoles["kaputt"].value, "true")

	def testCollectionFallback(self):
		t = base.parseFromString(rscdef.TableDef,
			"""<table id="foo">
				<dm>
					(testdm:testclass) {
						attr1: [
							@foo | isnich
							@nix | fallback
						]
					}
				</dm>
				<column name="foo"/>
				</table>""")
		self.assertEqual(t.annotations[0].type, "testdm:testclass")
		self.assertEqual(t.annotations[0].childRoles["attr1"]
			.children[0].value.name, "foo")
		self.assertEqual(t.annotations[0].childRoles["attr1"]
			.children[1].value, "fallback")

	def testColumnReference(self):
		t = base.parseFromString(rscdef.TableDef,
			"""<table id="foo">
				<dm>
					(testdm:testclass) {
						attr1: @col1
					}
				</dm><column name="col1" ucd="stuff"/></table>""")
		col = t.annotations[0].childRoles["attr1"].value
		self.assertEqual(col.ucd, "stuff")

	def testNULLAnnotation(self):
		t = base.parseFromString(rscdef.TableDef,
			"""<table id="foo">
				<dm>
					(testdm:testclass) {
						attr1: __NULL__
						attr2: red
						value: @col1
					}
				</dm><column name="col1" ucd="stuff"/></table>""")
		self.assertEqual(t.annotations[0].childRoles["attr2"].value, "red")
		self.assertFalse("attr1" in t.annotations[0].childRoles)


# Use this table (rather than _RealDMTable) to build tests against
# DMs we can control for testing purposes.
class _AnnotationTable(testhelpers.TestResource):
	def make(self, deps):
		td = base.parseFromString(rscdef.TableDef,
			"""<table id="foo">
					<dm>
						(dachstoy:Ruler) {
							width: @col1
							location:
								(dachstoy:Location) {
									x: 0.1
									y: @raj2000
									z: @dej2000
								}
							maker: [
								Oma "Opa Rudolf" @artisan]
						}
					</dm>
					<dm>
						(geojson:FeatureCollection) {
							feature: {
								geometry: sepcoo
								long: @raj2000
								lat: @col1
							}
						}
					</dm>
					<param name="artisan" type="text">Onkel Fritz</param>
					<column name="col1" ucd="stuff" type="text"/>
					<column name="raj2000"/>
					<column name="dej2000"/>
				</table>""")
		
		return rsc.TableForDef(td, rows=[
			{"col1": "1.5", "raj2000": 0.3, "dej2000": 3.1}])

_ANNOTATION_TABLE = _AnnotationTable()


class _DirectVOT(testhelpers.TestResource):
	resources = [("table", _ANNOTATION_TABLE)]

	def make(self, deps):
		return testhelpers.getXMLTree(votablewrite.getAsVOTable(	
			deps["table"],
			ctx=votablewrite.VOTableContext(version=(1,6))), debug=False)


class DirectSerTest(testhelpers.VerboseTest):
	resources = [("tree", _DirectVOT())]

	def testTestModelDefined(self):
		dmgroup = self.tree.xpath(
			"//VODML/MODEL[@name='dachstoy']")[0]
		self.assertEqual(
			dmgroup.get("url"),
			"http://docs.g-vo.org/dachstoy")

	def testNoExtraModels(self):
		self.assertEqual(2,  # this is missing ivoa: -- do we want it?
			len(self.tree.xpath("//MODEL")))

	def testTestclassInstancePresent(self):
		res = self.tree.xpath(
			"//TEMPLATES/INSTANCE[@dmtype='dachstoy:Ruler']")
		self.assertEqual(len(res), 1)
	
	def testLiteralSerialized(self):
		par = self.tree.xpath(
			"//INSTANCE[@dmtype='dachstoy:Location']"
			"/ATTRIBUTE[@dmrole='x']")[0]
		self.assertEqual(par.get("value"), "0.1")
		self.assertEqual(par.get("dmtype"), "ivoa:string")

	def testChildColumnAnnotated(self):
		# NOTE: this is currently the only test that actually checks the correct
		# location of the VODML container resource.
		fr = self.tree.xpath(
			"RESOURCE/RESOURCE[1]/VODML/TEMPLATES/"
			"INSTANCE[@dmtype='dachstoy:Ruler']"
			"/ATTRIBUTE[@dmrole='width']/@ref")[0]
		col = self.tree.getByID(fr)
		self.assertEqual(col.get("name"), "col1")

	def testNestedColumnAnnotated(self):
		fr = self.tree.xpath(
			"//INSTANCE[@dmtype='dachstoy:Location']/ATTRIBUTE[@dmrole='y']/@ref")[0]
		col = self.tree.getByID(fr)
		self.assertEqual(col.get("name"), "raj2000")

	def testCollectionLiterals(self):
		gr = self.tree.xpath(
			"//INSTANCE/ATTRIBUTE[@dmrole='maker']/COLLECTION")
		vals = testhelpers.pickSingle(gr).xpath("ATTRIBUTE/@value")
		self.assertEqual(len(vals), 2)
		self.assertEqual(vals[0], "Oma")
		self.assertEqual(vals[1], "Opa Rudolf")

	def testParamReferenced(self):
		gr = self.tree.xpath(
			"//TEMPLATES/INSTANCE/ATTRIBUTE[@dmrole='maker']"
			"/COLLECTION/ATTRIBUTE/@ref")
		paramref = testhelpers.pickSingle(gr)
		par = self.tree.getByID(paramref)
		self.assertEqual(par.get("value"), "Onkel Fritz")


class CopyTest(testhelpers.VerboseTest):
	resources = [("table", _ANNOTATION_TABLE)]

	def testParamDMRoleLink(self):
		ann = self.table.tableDef.getByName("artisan").dmRoles[0]()
		self.assertEqual(ann.name, "maker")

	def testColumnDMRoleLink(self):
		ann = self.table.tableDef.getByName("raj2000").dmRoles[0]()
		self.assertEqual(ann.name, "y")
	
	def testDMRolesCopying(self):
		col = self.table.tableDef.getByName("raj2000")
		colCopy = col.copy(None)
		self.assertEqual(colCopy.dmRoles.__class__.__name__,
			"OldRoles")

	def testSimpleCopy(self):
		newTD = self.table.tableDef.copy(None)
		ann = next(newTD.iterAnnotationsOfType("dachstoy:Ruler"))
		self.assertEqual(ann["maker"][0], "Oma")
		self.assertEqual(ann["maker"][2].value, newTD.getByName("artisan"))
		self.assertEqual(ann["width"].value, newTD.getByName("col1"))
		self.assertEqual(ann["location"]["x"], "0.1")
		self.assertEqual(ann["location"]["y"].value,
			newTD.getByName("raj2000"))

		ann = next(newTD.iterAnnotationsOfType("geojson:FeatureCollection"))
		self.assertEqual(ann["feature"]["geometry"], "sepcoo")
		self.assertEqual(ann["feature"]["long"].value,
			newTD.getByName("raj2000"))

	def testPartialCopy(self):
		newTD = self.table.tableDef.change(id="copy", columns=[
			self.table.tableDef.getByName("dej2000").copy(None)],
			params=[])
		ann = next(newTD.iterAnnotationsOfType("dachstoy:Ruler"))
		self.assertEqual(ann["maker"][0], "Oma")
		self.assertEqual(len(ann["maker"]), 2)
		self.assertEqual(ann["location"]["z"].value,
			newTD.getByName("dej2000"))
		self.assertFalse("y" in ann["location"])
		# the following assertion states that annotations without any
		# columns/params referenced are not copied into the destination
		# table.
		self.assertEqual(len(newTD.annotations), 1)
	
	def testOutputTableConstruction(self):
		newTD = base.makeStruct(svcs.OutputTableDef,
			columns=[
				self.table.tableDef.getByName("raj2000"),
				self.table.tableDef.getByName("dej2000")],
			params=[
				self.table.tableDef.getByName("artisan")])
		ann = next(newTD.iterAnnotationsOfType("dachstoy:Ruler"))
		self.assertEqual(ann["maker"][0], "Oma")
		self.assertEqual(ann["location"]["z"].value,
			newTD.getByName("dej2000"))
		self.assertEqual(len(ann["maker"]), 3)

		ann = next(newTD.iterAnnotationsOfType("geojson:FeatureCollection"))
		self.assertEqual(ann["feature"]["geometry"], "sepcoo")
		self.assertEqual(ann["feature"]["long"].value,
			newTD.getByName("raj2000"))


# When you're looking for a table to pollute with custom
# constructs, use _AnnotationTable.
class _RealDMTable(testhelpers.TestResource):
	# well, of course, these are not the real DMs at this point.
	# as the come out, they should go in here, and this should
	# be used to test validation.
	def make(self, deps):
		rd = base.parseFromString(rscdesc.RD,
# this needs a resource container since we need id resolution
			"""
<resource schema="test">
<table id="foo">
	<dm>
		(ds:DataSet) {
			ProductType: timeseries
			calibLevel: 1
			dataId: @pubDID
			creator: [
				"Joe Cool"
				"Charlie Brown"
			]
			observationID: @pubDID
			target: {
				name: @targetName
				position: @targetPos
			}
		}
	</dm>

	<dm id="targetPos">
		(votable:Coordinates) {
			spatial: {
				frame: {
					referenceSystem: ICRS
				}
				c1: @raj2000
				c2: @dej2000
			}
			temporal: {
				frame: {
					timeScale: TT
					referencePosition: HELIOCENTER
				}
				value: @HJD
			}
		}
	</dm>

	<param name="raj2000" type="double precision"
		ucd="pos.eq.ra" unit="deg"/>
	<param name="dej2000" type="double precision"
		ucd="pos.eq.dec" unit="deg"/>
	<param name="targetName" type="text"/>
	<param name="pubDID" type="text"/>

	<column name="HJD" type="double precision"/>

</table>
</resource>""")
		
		t = rsc.TableForDef(rd.tables[0], rows=[
			{"HJD": 2000000.125}])
		t.setParam("raj2000", 230)
		t.setParam("dej2000", -45)
		return t

_REAL_DM_TABLE = _RealDMTable()


class _RealDMVOT(testhelpers.TestResource):
	resources = [("table", _REAL_DM_TABLE)]

	def make(self, deps):
		return testhelpers.getXMLTree(votablewrite.getAsVOTable(	
			deps["table"],
			ctx=votablewrite.VOTableContext(version=(1,6))), debug=False)

_REAL_DM_VOT = _RealDMVOT()


class ObjReftest(testhelpers.VerboseTest):
	resources = [("table", _REAL_DM_TABLE),
		("serialized", _REAL_DM_VOT)]

	def testInterInstanceAnnotation(self):
		ds = next(self.table.tableDef.iterAnnotationsOfType("ds:DataSet"))
		pos = ds["target"]["position"].objectReferenced
		self.assertEqual(pos["spatial"]["frame"]["referenceSystem"],
			"ICRS")
		self.assertEqual(pos["spatial"]["c1"].value.name, "raj2000")

	def testInstanceRefSerialisation(self):
		groupref = self.serialized.xpath(
			"//INSTANCE[@dmtype='ds:DataSet']/ATTRIBUTE[@dmrole='target']"
			"/INSTANCE/ATTRIBUTE[@dmrole='position']/REFERENCE")[0]
		pos = self.serialized.xpath("//*[@ID='{}']".format(
			groupref.get("dmref")))[0]
		self.assertEqual(pos.get("dmtype"), "votable:Coordinates")

		self.assertEqual(pos.xpath("ATTRIBUTE[@dmrole='temporal']"
			"/INSTANCE/ATTRIBUTE[@dmrole='frame']/INSTANCE"
			"/ATTRIBUTE[@dmrole='referencePosition']/@value")[0],
			"HELIOCENTER")


class VOTWithPhotcals(testhelpers.TestResource):
	def make(self, deps):
		rd = base.parseFromString(rscdesc.RD,
			"""
<resource schema="test">
<table id="foo">
	<dm>
		(phot:PhotCal) {
			filterIdentifier: "2MASS/Ks"
			zeroPointFlux: 1e-11
			magnitudeSystem: Vega
			effectiveWavelength: 2.2e-6
			value: @kmag
		}
	</dm>

	<dm>
		(phot:PhotCal) {
			filterIdentifier: "Johnson/V"
			effectiveWavelength: 6.2e-7
			value: @vmag
		}
	</dm>

	<column name="kmag" ucd="phot.mag;em.IR"/>
	<column name="vmag" ucd="phot.mag;em.opt"/>
</table>
</resource>""")
		
		t = rsc.TableForDef(rd.tables[0], rows=[
			{"kmag": 10.3, "vmag": 8.1}])
		res = formats.getFormatted("vodml", t)
		return res, testhelpers.getXMLTree(res, debug=False)


class PhotcalTest(testhelpers.VerboseTest):
	# this is for Ada's temporary PHOTCAL group hack from
	# http://ivoa.net/documents/Notes/LightCurveTimeSeries//index.html

	resources = [("l_t", VOTWithPhotcals())]

	def testVODMLPresent(self):
		tree = self.l_t[1]
		kmagGroup = tree.xpath("//INSTANCE[@dmtype='phot:PhotCal'"
			"and ATTRIBUTE[@dmrole='value']/@ref='kmag']")[0]
		self.assertEqual(
			kmagGroup.xpath("ATTRIBUTE[@dmrole='filterIdentifier']/@value")[0],
			"2MASS/Ks")
		self.assertEqual(
			kmagGroup.xpath("ATTRIBUTE[@dmrole='zeroPointFlux']/@value")[0],
			"1e-11")
		self.assertEqual(
			kmagGroup.xpath("ATTRIBUTE[@dmrole='magnitudeSystem']/@value")[0],
			"Vega")
		self.assertEqual(
			kmagGroup.xpath("ATTRIBUTE[@dmrole='effectiveWavelength']/@value")[0],
			"2.2e-6")

		vmagGroup = tree.xpath("//INSTANCE[@dmtype='phot:PhotCal'"
			"and ATTRIBUTE[@dmrole='value']/@ref='vmag']")[0]
		self.assertEqual(
			vmagGroup.xpath("ATTRIBUTE[@dmrole='filterIdentifier']/@value")[0],
			"Johnson/V")

	def testPhotcalGroupsPresent(self):
		self.assertEqual(
			["phot_def", "phot_def-02"],
			[n.get("ID") for n in self.l_t[1].xpath("//GROUP[@name='photcal']")])

	def testAllParamsSerialised(self):
		self.assertEqual(
			len(self.l_t[1].xpath(
				"RESOURCE/GROUP[@ID='phot_def']/PARAM")),
			4)
	
	def testColumnReferencing(self):
		tree = self.l_t[1]
		references = tree.xpath(
			"RESOURCE/GROUP/FIELDref/@ref")

		col = tree.getByID(references[0])
		self.assertEqual(col.get("ucd"), "phot.mag;em.IR")

		col = tree.getByID(references[1])
		self.assertEqual(col.get("ucd"), "phot.mag;em.opt")

	def testFloatSerialization(self):
		self.assertEqual(
			self.l_t[1].xpath("//PARAM[@name='effectiveWavelength']/@value"),
			["2.2e-6", "6.2e-7"])

	def testStupidBackReferences(self):
		self.assertEqual(
			self.l_t[1].xpath("//FIELD/@ref"),
			["phot_def", "phot_def-02"])


if __name__=="__main__":
	testhelpers.main(DirectSerTest)
