"""
Tests having to do with the registry code.
"""

#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.


import datetime
import re
import os

from gavo.helpers import testhelpers

from gavo import api
from gavo import base
from gavo import registry
from gavo import rscdesc
from gavo import svcs
from gavo import utils
from gavo.base import meta
from gavo.helpers import testtricks
from gavo.registry import builders
from gavo.registry import common
from gavo.registry import capabilities
from gavo.registry import nonservice
from gavo.registry import oaiinter
from gavo.registry import publication
from gavo.registry import tableset
from gavo.utils import ElementTree
from gavo.registry.model import OAI
from gavo.registry.model import VOR
from gavo.registry.model import VS
from gavo.web import vosi

import tresc


def getGetRecordResponse(resob):
	"""returns XML and parsedXML as returned from an OAI getRecord
	call that retrieves resob.
	"""
	pars = {"verb": "GetRecord", "metadataPrefix": "ivo_vor"}
	source = OAI.PMH[
		oaiinter.getResponseHeaders(pars),
		builders.getVOGetRecordElement(resob)].render()
	return source, testhelpers.getXMLTree(source, debug=False)


class RegistryModelTest(testhelpers.VerboseTest):
	def testVSNamespaces(self):
		from gavo.registry import model
		self.assertEqual(model.VS.ucd()._prefix, "vs")

	def testVOTableDataType(self):
		self.assertEqual(
			testhelpers.cleanXML(tableset.voTableDataTypeFactory("char").render()),
			'<dataType arraysize="1" xsi:type="vs:VOTableType">char</dataType>')
		self.assertEqual(
			testhelpers.cleanXML(tableset.voTableDataTypeFactory("integer").render()),
			'<dataType xsi:type="vs:VOTableType">int</dataType>')
		self.assertEqual(
			testhelpers.cleanXML(tableset.voTableDataTypeFactory("text").render()),
			'<dataType arraysize="*" xsi:type="vs:VOTableType">char</dataType>')
		self.assertEqual(
			testhelpers.cleanXML(tableset.voTableDataTypeFactory(
				"integer[20]").render()),
			'<dataType arraysize="20" xsi:type="vs:VOTableType">int</dataType>')
	
	def testUsefulRecordRepr(self):
		el = VOR.Organisation[VOR.identifier["ivo://example.org/myorg"]]
		self.assertEqual(repr(el), "<Organisation: ivo://example.org/myorg>")

	def testRecordReprDoesNotBreak(self):
		repr(VOR.Organisation[VOR.shortName["nonid"],
			VOR.title["a test org"], VOR.altIdentifier["test:testing"]])

	def testDataTypeDeclaration(self):
		literal = VS.dataType(xsi_type="vs:SimpleDataType")["char"].render()
		tree = testhelpers.getXMLTree(literal, debug=False)
		self.assertEqual(tree.text, "char")
		self.assertEqual(tree.attrib[
			'{http://www.w3.org/2001/XMLSchema-instance}type'], 'vs:SimpleDataType')


class DeletedTest(testhelpers.VerboseTest):
	"""tests for deletion of record doing roughly what's necessary.
	"""
# All these things need to run in sequence.  Lousy.
	rdId = 'data/pubtest'

	resources = [("connection", tresc.dbConnection)]

#	def tearDown(self):
#		publication._purgeFromServiceTables(self.rdId, self.connection)
#		self.connection.commit()

	def _makeDeletedRecord(self):
		return base.makeStruct(nonservice.DeletedResource,
			resTuple={"sourceRD": "foo", "resId": "bar", "recTimestamp":
				datetime.datetime(2010, 10, 10, 10, 10, 10),
				"ivoid": "ivo://%s/foo/bar"%base.getConfig("ivoa", "authority")})

	def testResob(self):
		dr = self._makeDeletedRecord()
		self.assertEqual(base.getMetaText(dr, "identifier"),
			"ivo://%s/foo/bar"%base.getConfig("ivoa", "authority"))
		self.assertEqual(base.getMetaText(dr, "status"), "deleted")

	def testResrec(self):
		dr = self._makeDeletedRecord()
		oairec = builders.getVOResourceElement(dr).render()
		self.assertTrue(b'<oai:header status="deleted"><oai:identifier>'
			b'ivo://x-testing/foo/bar</oai:identifier><oai:datestamp>'
			b'2010-10-10T10:10:10Z</oai:datestamp></oai:header></oai:record>'
			in oairec)

	def _createPublication(self):
		rd = api.getRD(self.rdId)
		publication.updateServiceList([rd], connection=self.connection)
		self.connection.commit()

	def _deletePublication(self):
		rd = api.getRD(self.rdId)
		del rd.services[0]
		publication.updateServiceList([rd], connection=self.connection)
		self.connection.commit()

	def _assertPublished(self):
		# see if oaiinter functions see new service
		yesterday = datetime.datetime.today()+ datetime.timedelta(days=-1)
		matches = [tup for tup in oaiinter.getMatchingRestups(
			{"from": yesterday.strftime(utils.isoTimestampFmt)})
			if tup["sourceRD"]==self.rdId]
		self.assertTrue(len(matches)==1, "Publication did not write record.")
		match = matches[0]
		self.assertTrue(
			(datetime.datetime.utcnow()-match["recTimestamp"]).seconds==0,
			"Stale publication record?  Your machine can't be that slow")

	def _assertUnpublished(self):
		yesterday = datetime.datetime.today()+ datetime.timedelta(days=-1)
		matches = [tup for tup in oaiinter.getMatchingRestups(
			{"from": yesterday.strftime(utils.isoTimestampFmt)})
			if tup["sourceRD"]==self.rdId]
		self.assertTrue(len(matches)==1, "Unpublication deleted record.")
		match = matches[0]
		self.assertTrue(match["deleted"],
			"Unpublication didn't set deleted flag.")
		
	def _assertCanBuildResob(self):
		restup = [tup for tup in oaiinter.getMatchingRestups({})
			if tup["sourceRD"]==self.rdId][0]
		resob = registry.getResobFromRestup(restup)
		self.assertEqual(resob.resType, "deleted")
		dcRepr = builders.getDCResourceElement(resob).render()
		self.assertTrue(b'<oai:header status="deleted"' in dcRepr)
		self.assertTrue(
			b"<oai:identifier>ivo://x-testing/data/pubtest/moribund<"
			in dcRepr)
		voRepr = builders.getVOResourceElement(resob).render()
		self.assertTrue(b'<oai:header status="deleted"' in voRepr)
		self.assertTrue(
			b"<oai:identifier>ivo://x-testing/data/pubtest/moribund<"
			in voRepr)

	def testBigAndUgly(self):
		self._createPublication()
		self._assertPublished()

		# Must work a second time, overwriting the old junk
		self._createPublication()
		self._assertPublished()

		# reset the rectimestamp so the test fails if delete doesn't
		# update it.
		self.connection.execute("update dc.resources set rectimestamp='2010-01-01'"
				" where ivoid='ivo://x-testing/data/pubtest/moribund'")
		self.connection.commit()

		# Now nuke the record
		self._deletePublication()
		self._assertUnpublished()
		
		# And create a resource object from it
		self._assertCanBuildResob()


class _TAPCapabilityElement(testhelpers.TestResource):
	resources = [
		("oc", tresc.obscoreTable)]

	def make(self, deps):
		publication = base.caches.getRD("//tap"
			).getById("run").publications[0]
		res = vosi.CAP.capabilities[
			capabilities.getCapabilityElement(publication)].render()
		return res, testhelpers.getXMLTree(res, debug=False)


class TAPCapabilityTest(testhelpers.VerboseTest):
	resources = [
		("cap", _TAPCapabilityElement())]

	def testObscoreDeclared(self):
		for el in self.cap[1].findall("capability/dataModel"):
			if el.get("ivo-id")=="ivo://ivoa.net/std/obscore#core-1.1":
				self.assertEqual(el.text, "Obscore-1.1")
				break
		else:
			raise AssertionError("Obscore 1.1 not in the declared DMs of TAP Cap")
	
	def testADQLAvailable(self):
		el = self.cap[1].find("capability/language[name='ADQL']")
		self.assertEqual(
			el.xpath("version[.='2.0']")[0].get("ivo-id"),
			"ivo://ivoa.net/std/ADQL#v2.0")

	def testADQLVersion21Present(self):
		el = self.cap[1].xpath("capability/language[name='ADQL']/version[.='2.1']")
		self.assertEqual(
			el[0].get("ivo-id"),
			"ivo://ivoa.net/std/ADQL#v2.1")

	def testUDFDefined(self):
		parent = self.cap[1].find("capability/language/languageFeatures["
			"@type='ivo://ivoa.net/std/TAPRegExt#features-udf']")
		for feature in parent.findall("feature"):
			if feature.find("form").text.startswith("gavo_match(pattern"):
				self.assertTrue(feature.find("description").text.startswith(
					"gavo_match returns 1 if the POSIX regular"))
				break
		else:
			raise AssertionError("No gavo_match UDF declared")

	def testPolymorphicUDF(self):
		parent = self.cap[1].find("capability/language/languageFeatures["
			"@type='ivo://ivoa.net/std/TAPRegExt#features-udf']")
		specconvs = []
		for feature in parent.findall("feature"):
			if feature.find("form").text.startswith("gavo_specconv"):
				specconvs.append(feature)
		self.assertEqual(len(feature), 2)

	def testGeometryDefined(self):
		parent = self.cap[1].find("capability/language/languageFeatures["
			"@type='ivo://ivoa.net/std/TAPRegExt#features-adqlgeo']")
		for feature in parent.findall("feature"):
			if feature.find("form").text=='CIRCLE':
				break
		else:
			raise AssertionError("No CIRCLE ADQL geometry found")
	
	def testBinary2Defined(self):
		el = self.cap[1].xpath("capability/outputFormat"
			"[@ivo-id='ivo://ivoa.net/std/TAPRegExt#output-votable-binary2']")[0]
		self.assertEqual(el.find("mime").text,
			"application/x-votable+xml;serialization=BINARY2")
		self.assertEqual(el.xpath("alias")[0].text,
			"votable/b2")
	
	def testVotableTDDefined(self):
		el = self.cap[1].xpath("capability/outputFormat"
			"[@ivo-id='ivo://ivoa.net/std/TAPRegExt#output-votable-td']")[0]
		self.assertEqual("#".join(e.text for e in el.xpath("mime")),
			"application/x-votable+xml;serialization=TABLEDATA")
		self.assertEqual(len(el.xpath("alias[.='votable/td']")), 1)

	def testGeojsonPresent(self):
		el = self.cap[1].xpath("capability/outputFormat[alias='geojson']")[0]
		self.assertEqual(el.xpath("mime")[0].text, "application/geo+json")


class _SSACapabilityElement(testhelpers.TestResource):
	def make(self, deps):
		publication = testhelpers.getTestRD("ssatest"
			).getById("s").publications[0]
		res = vosi.CAP.capabilities[
			capabilities.getCapabilityElement(publication)].render()
		return res, testhelpers.getXMLTree(res, debug=False)[0]


class SSAPCapabilityTest(testhelpers.VerboseTest, testtricks.XSDTestMixin):
	resources = [("textAndTree", _SSACapabilityElement())]

	def testValid(self):
		self.assertValidates(self.textAndTree[0])
	
	def testCapabilityAttributes(self):
		tree = self.textAndTree[1]
		self.assertEqual(tree.attrib["standardID"], 'ivo://ivoa.net/std/ssa')
		self.assertEqual(
			tree.attrib['{http://www.w3.org/2001/XMLSchema-instance}type'],
			'ssap:SimpleSpectralAccess')
	
	def testInterfaceIsStandard(self):
		intf = self.textAndTree[1].find("interface")
		self.assertEqual(intf.attrib["role"], "std")
	
	def testInterfaceHasStandardParam(self):
		for paramEl in self.textAndTree[1].findall("interface/param"):
			if paramEl.find("name").text=="BAND":
				break
		else:
			raise AssertionError("No BAND input parameter in SSAP interface")
		self.assertEqual(paramEl.attrib["std"], "true")
		self.assertEqual(paramEl.find("unit").text, "m")
	
	def testInterfaceHasLocalParam(self):
		for paramEl in self.textAndTree[1].findall("interface/param"):
			if paramEl.find("name").text=="excellence":
				break
		else:
			raise AssertionError("No excellence input parameter in SSAP interface")
		self.assertEqual(paramEl.attrib["std"], "false")
		self.assertEqual(paramEl.find("description").text, "random number")

	def testMaxRecordsReflectsConfig(self):
		self.assertEqual(int(self.textAndTree[1].find("maxRecords").text),
			base.getConfig("ivoa", "dalHardLimit"))

	def testTestQuery(self):
		self.assertEqual(self.textAndTree[1].find("testQuery/queryDataCmd").text,
			"TARGETNAME=alpha%20Boo")

	def testRecordCreationFailsOnMissingMeta(self):
		publication = testhelpers.getTestRD("ssatest"
			).getById("s").publications[0]
		publication.parent.delMeta("ssap.testQuery")
		self.assertRaisesWithMsg(base.NoMetaKey,
			utils.EqualingRE('On <Service s at .*data/ssatest.rd, line 150>:'
				' No meta item ssap.testQuery'),
			capabilities.getCapabilityElement,
			(publication,))

	def testNestedMeta(self):
		with testtricks.testFile(
				os.path.join(base.getConfig("inputsDir"), "data", "tmp.rd"),
			"""<resource schema="test" resdir=".">
				<service id="t" original="data/ssatest#s">
				<publish render="ssap.xml"/>
				<meta name="ssap">
					<meta name="dataSource">you bet</meta>
					<meta name="testQuery">HAHA.</meta>
				</meta></service></resource>"""):
			res = capabilities.getCapabilityElement(testhelpers.getTestRD("tmp"
				).getById("t").publications[0]).render()
			self.assertTrue(b'<dataSource>you bet</dataSource>' in res)

	def testSimpleDataTypeUsed(self):
		for dt in self.textAndTree[1].findall("interface/param/dataType"):
			self.assertEqual(
				dt.attrib["{http://www.w3.org/2001/XMLSchema-instance}type"],
				"vs:SimpleDataType")


class _SIACapabilityElement(testhelpers.TestResource):
	def make(self, deps):
		publication = testhelpers.getTestRD("test"
			).getById("pgsiapsvc").publications[0]
		res = capabilities.getCapabilityElement(publication).render()
		return res, testhelpers.getXMLTree(res, debug=False)


class SIAPCapabilityTest(testhelpers.VerboseTest):
	resources = [("txNTree", _SIACapabilityElement())]

	def testNoEmptyMax(self):
		self.assertFalse(self.txNTree[1].xpath("//maxImageExtent/lat"),
			"maxImageExtent/lat should be empty but is not")

	def testNoFilledMax(self):
		self.assertEqual(
			self.txNTree[1].xpath("//maxImageExtent/long")[0].text,
			"10")
	
	def testImageSizeAtomic(self):
		children = self.txNTree[1].xpath("//maxImageSize")
		self.assertEqual(testhelpers.pickSingle(children).text, "3000")

	def testTestQuery(self):
		tqEl = self.txNTree[1].xpath("/capability/testQuery")[0]
		self.assertEqual(len(tqEl), 2)
		longs = tqEl.xpath("*/long")
		self.assertEqual([l.text for l in longs], ["10", "0.4"])


class _ObscoreCoverage(testhelpers.TestResource):
	resources = [("obscore", tresc.obscoreTable), ("conn", tresc.dbConnection)]

	def make(self, deps):
		# this doesn't have return a value; it just runs dachs limits
		from gavo.user import limits
		limits.updateForRD(
			base.caches.getRD("//obscore"),
			deps["conn"],
			100,
			False)
		base.caches.clearForName("__system__/obscore")
		return True


class _SIA2Record(testhelpers.TestResource):
	resources = [("obscore_covered", _ObscoreCoverage())]

	def make(self, deps):
		base.caches.clearForName("__system__/siap2")
		with testhelpers.userconfigContent("""
					<NXSTREAM id="sitewidesiap2-extras">
						<meta name="description">
							This is just an illusion.
						</meta>
						<meta>
							testQuery.pos.ra: 3.14
							testQuery.pos.dec: 0.5
							testQuery.size.ra: 0.5
							testQuery.size.dec: 0.5
						</meta>
						<meta name="creationDate">2016-08-15T12:40:00</meta>
						<meta name="subject">Images</meta>
						<meta name="shortName">unittest SIA2</meta>
						<meta name="title">DaCHS unittests SIAv2 service</meta>
						<meta name="sia.type">Pointed</meta>
					</NXSTREAM>"""):
			resOb = base.resolveCrossId("//siap2#sitewide")
			res = builders.getVORMetadataElement(resOb).render()
			return res, testhelpers.getXMLTree(res, debug=False)


class SIAP2RecordTest(testhelpers.VerboseTest, testhelpers.XSDTestMixin):
	resources = [("txNTree", _SIA2Record())]

	def testNoEmptyMax(self):
		self.assertFalse(self.txNTree[1].xpath("//maxImageExtent/lat"),
			"maxImageExtent/lat should be empty but is not")

	def testTestQuery(self):
		tqEl = self.txNTree[1].xpath("capability/testQuery")[0]
		self.assertEqual(len(tqEl), 2)
		longs = tqEl.xpath("*/long")
		self.assertEqual([l.text for l in longs], ["3.14", "0.5"])

	def testStandardId(self):
		self.assertEqual(self.txNTree[1].xpath("capability/@standardID")[0],
			"ivo://ivoa.net/std/sia#query-2.0")
	
	def testValid(self):
		self.assertValidates(self.txNTree[0])

	def testDerivedServedByObscore(self):
		self.assertEqual(
			self.txNTree[1].xpath("content/relationship["
				"relationshipType='IsServedBy']/relatedResource/@ivo-id")[0],
			"ivo://x-testing/__system__/obscore/obscore")
	
	def testSpatialCoverage(self):
		self.assertEqual(
			self.txNTree[1].xpath("coverage/spatial")[0].text.split()[0],
			"4/1087")
	
	def testTemporalCoverage(self):
		self.assertEqual(
			self.txNTree[1].xpath("coverage/temporal")[0].text,
			"55300 55462")

	def testSpectralCoverage(self):
		self.assertEqual(
			self.txNTree[1].xpath("coverage/spectral")[0].text,
			"1.203177382e-27 1.986445857e-18")


class AuthorityTest(testhelpers.VerboseTest):
# This test will fail until defaultmeta.txt has the necessary entries
# and //services is published
	def testAuthorityResob(self):
		authId = "ivo://%s"%base.getConfig("ivoa", "authority")
		resob = registry.getResobFromIdentifier(authId)
		self.assertEqual(base.getMetaText(resob, "identifier"), authId)
		self.assertFalse(resob.getMeta("title") is None)
		self.assertFalse(resob.getMeta("_metadataUpdated") is None)
		self.assertEqual(base.getMetaText(resob, "sets"), "ivo_managed")
	
	def testAuthorityVORes(self):
		resob = registry.getResobFromIdentifier(
			"ivo://%s"%base.getConfig("ivoa", "authority"))
		resrec = builders.getVORMetadataElement(resob).render()
		tree = ElementTree.fromstring(resrec)
		self.assertEqual(tree.find("managingOrg").text,
			"Your organisation's name")
		self.assertTrue(b'created="' in resrec)


class _StandardsRec(testhelpers.TestResource):
	def make(self, ignored):
		class Container(meta.MetaMixin):
			resType = "standard"
			rd = base.caches.getRD("//services")
		container = Container()
		container.setMetaParent(container.rd)

		meta.parseMetaStream(container, """
			recTimestamp: 2010-10-10T10:10:10Z
			sets: ivo_managed
			status: active
			title: a test standard
			subject: testing
			referenceURL: http://bar
			identifier: ivo://foo.bar
			doi: 10.5072/teststuff
			endorsedVersion: 1.1
			endorsedVersion.status: wd
			endorsedVersion.use: preferred
			endorsedVersion: 1.0
			endorsedVersion.status: rec
			endorsedVersion.use: deprecated
			deprecated: rather use something else
			key:
			key.name: bar1
			key.description: This one's open
			key:
			key.name: bar2
			key.description: This one's closed
			""")
		return getGetRecordResponse(container)
	

class StandardsTest(testhelpers.VerboseTest, testtricks.XSDTestMixin):
	resources = [("srcAndTree", _StandardsRec())]
	
	def testIsValid(self):
		self.assertValidates(self.srcAndTree[0])

	def testTwoEndorsedVersions(self):
		self.assertEqual(len(self.srcAndTree[1].xpath("//endorsedVersion")), 2)
	
	def testEndorsedVersionMetaPresent(self):
		el = self.srcAndTree[1].xpath("//endorsedVersion")[0]
		self.assertEqual(el.get("status"), "wd")
		self.assertEqual(el.get("use"), "preferred")
		self.assertEqual(el.text, "1.1")
	
	def testDeprecated(self):
		self.assertEqual(self.srcAndTree[1].xpath("//deprecated")[0].text,
			"rather use something else")

	def testTwoKeys(self):
		self.assertEqual(len(self.srcAndTree[1].xpath("//key")), 2)

	def testKeyStructure(self):
		el = self.srcAndTree[1].xpath("//key")[0]
		self.assertEqual(el[0].tag, "name")
		self.assertEqual(el[0].text, "bar1")
		self.assertEqual(el[1].tag, "description")
		self.assertEqual(el[1].text, "This one's open")

	def testAltIdentifiers(self):
		els = self.srcAndTree[1].xpath("//metadata/Resource/altIdentifier")
		self.assertEqual([el.text for el in els], [
			"doi:10.5072/teststuff"])


class _DocRec(testhelpers.TestResource):
	def make(self, ignored):
		base.setConfig("ivoa", "registerAlternative", "True")
		try:
			rd = base.parseFromString(rscdesc.RD,
				rf"""<resource schema="test">
					<macDef name="waran">Warning: Waran</macDef>
					<meta name="description">We should not have any backslashes
						here: \waran.</meta>
					{tresc.A_RES_REC}
				</resource>""")

			return getGetRecordResponse(rd.getById("rec"))
		finally:
			base.setConfig("ivoa", "registerAlternative", "False")


class DocumentResTest(testhelpers.VerboseTest, testtricks.XSDTestMixin):
	#	Which we're abusing as a test for https mirror url generation, too,
	# because it fits so well.
	resources = [("srcAndTree", _DocRec())]
	
	def testIsValid(self):
		self.assertValidates(self.srcAndTree[0])
	
	def testEnglishIsPrimary(self):
		lang = self.srcAndTree[1].xpath("//capability[1]/languageCode")[0]
		self.assertEqual(lang.text, "en")

	def testLocTitle(self):
		els = self.srcAndTree[1].xpath(
			"//capability[languageCode='de']/locTitle")
		self.assertEqual(testhelpers.pickSingle(els).text, "Ja, geh zu.")

	def testAccessRenderedURLs(self):
		urls = self.srcAndTree[1].xpath("//interface[@role='rendered']/accessURL")
		self.assertEqual(len(urls), 3)
		self.assertEqual(urls[1].text, "http://foo.fr/ycnaf/cod.pdf")

	def testExtensionInferredMediaType(self):
		self.assertEqual(
			self.srcAndTree[1].xpath(
				"//interface[accessURL='http://foo.fr/ycnaf/cod.pdf']"
					"/resultType")[0].text,
			"application/pdf")

	def testExtensionExplicitMediaType(self):
		self.assertEqual(
			self.srcAndTree[1].xpath(
				"//interface[accessURL='http://localhost:8080/"
					"fancy/doc']/resultType")[0].text,
			"application/msword")

	def testSourceURL(self):
		urls = self.srcAndTree[1].xpath("//interface[@role='source']/accessURL")
		self.assertEqual(len(urls), 2)
		self.assertEqual(urls[0].text, "svn://foo.us/docs/src/fancy")
		self.assertEqual(urls[1].text, "git+ssh://foo.fr/scod/src/ycnaf")

	def testRelationships(self):
		relations = self.srcAndTree[1].xpath("//relationship")
		t = testhelpers.pickSingle(relations).xpath("relationshipType")[0]

		self.assertEqual(t.text, "Cites")
		ids = relations[0].xpath("relatedResource")
		self.assertEqual(ids[0].text, "Sample useful resource")
		self.assertEqual(ids[1].attrib["ivo-id"], "ivo://crapola/work")

	def testAltIdentifier(self):
		rel = testhelpers.pickSingle(self.srcAndTree[1].xpath("//relationship[relationshipType='Cites']/relatedResource[@altIdentifier!='']"))
		self.assertEqual(rel.get("altIdentifier"), "ascl:1101.010")

	def testNewsRenderingWithRole(self):
		sillyDates = self.srcAndTree[1].xpath("//date[@role='silly']")
		self.assertEqual(testhelpers.pickSingle(sillyDates).text, "2010-10-10")
	
	def testNewsRenderingDefaultRole(self):
		upDates = self.srcAndTree[1].xpath("//date[@role='Updated']")
		# there's two updates: one from news, another from the _dataUpdated meta
		self.assertEqual(len(upDates), 2)
		self.assertEqual(
			set(e.text for e in upDates),
			set(["2011-11-11", "2015-10-10T09:09:09Z"]))

	def testHTTPSMirrorForUs(self):
		urls = self.srcAndTree[1].xpath("//interface[@role='rendered']/mirrorURL")
		self.assertEqual(urls[0].text, "https://localhost/fancy/doc")

	def testNoHTTPSMirrorForOthers(self):
		urls = self.srcAndTree[1].xpath("//capability[1]/interface/mirrorURL")
		self.assertEqual(len(urls), 0)

	def testSetSpec(self):
		self.assertEqual(set(n.text for n in
			self.srcAndTree[1].xpath("GetRecord/record/header/setSpec")),
			set(["ivo_managed", "local"]))

	def testMacroExpanded(self):
		self.assertEqual(
			self.srcAndTree[1].xpath(
			"GetRecord/record/metadata/Resource/content/description")[0].text,
			"We should not have any backslashes here: Warning: Waran.")

	def testTablesetPresent(self):
		self.assertEqual(
			{m.text for m in self.srcAndTree[1].xpath("//tableset/schema/name")},
			{"test", "dc"})
		self.assertEqual(
			self.srcAndTree[1].xpath("//table/column[name='anint']/name")[0].text,
			"anint")


class _ResourceGrammar(testhelpers.TestResource):
	def make(self, ignored):
		return base.makeStruct(publication.RDRscRecGrammar)

_resourceGrammar = _ResourceGrammar()

	
class DataPublicationMetaTest(testhelpers.VerboseTest):
# Tests concerning metadata handling with the table data registry interface
	resources = [("conn", tresc.dbConnection), ("resg", _resourceGrammar)]

	def testMinimalMeta(self):
		rd = base.parseFromString(rscdesc.RD, """<resource schema="data">
			<table id="ronk">
				<register sets="ivo_managed,local"/>
			</table></resource>""")
		self.assertRaisesWithMsg(base.MetaValidationError,
			'Meta structure on ronk (within IO:\'<resource schema="data"> <table id="ronk"> <register ...\', line 2)'
			" did not validate: Meta key creationDate missing,"
			" Meta key description missing, Meta key subject missing",
			list,
			(self.resg.parse(rd),))

	def testIterDataTable(self):
		rd = base.parseFromString(rscdesc.RD, f"""<resource schema="data">
			{tresc.MINIMAL_META}
			<meta name="referenceURL">b</meta>
			<table id="ronk"><register sets="ivo_managed,local"/>
			</table></resource>""")
		recs = list(self.resg.parse(rd))

		self.assertEqual(len(recs), 6)
		roles, sets = set(), set()

		for role, rec in recs:
			roles.add(role)
			if role=="resources":
				self.assertEqual(rec["resId"], "ronk")
			elif role=="sets":
				sets.add(rec["setName"])
			elif role=="interfaces":
				self.assertEqual(rec["referenceURL"],
				'http://localhost:8080/tableinfo/data.ronk')
				self.assertEqual(rec["accessURL"],
					'http://localhost:8080/tableinfo/data.ronk?tapinfo=True')
			elif role=="subjects":
				self.assertEqual(rec["subject"], "a")
			elif role=="authors":
				self.assertEqual(rec["author"], "Could be same as contact.name")

		self.assertEqual(roles, {"authors", "subjects", "sets", "interfaces",
			"sets", "resources"})
		self.assertEqual(sets, {"ivo_managed", "local"})

	def testIterDataData(self):
		rd = base.parseFromString(rscdesc.RD, f"""<resource schema="data">
			{tresc.MINIMAL_META}
			<meta name="referenceURL">b</meta>
			<table id="ronk"/><table id="funk"/>
			<data id="ronkcoll"><register sets="ivo_managed,local"/>
			<make table="ronk"/><make table="funk"/>
			</data></resource>""")
		rd.sourceId = "data/test"
		recs = list(self.resg.parse(rd))

		roles, sets = set(), set()

		for role, rec in recs:
			roles.add(role)
			if role=="resources":
				self.assertEqual(rec["resId"], "ronkcoll")
			elif role=="sets":
				sets.add(rec["setName"])
			elif role=="interfaces":
				self.assertEqual(rec["referenceURL"],
				'http://localhost:8080/browse/data/test')
				self.assertEqual(rec["accessURL"],
					'http://localhost:8080/browse/data/test?tapinfo=True')
			elif role=="subjects":
				self.assertEqual(rec["subject"], "a")
			elif role=="authors":
				self.assertEqual(rec["author"], "Could be same as contact.name")

		self.assertEqual(roles, {"authors", "subjects", "sets", "interfaces",
			"sets", "resources"})
		self.assertEqual(sets, {"ivo_managed", "local"})

	def testRejectedWithoutId(self):
		self.assertRaisesWithMsg(base.StructureError,
			'At IO:\'<resource schema="data"> <table><register sets="ivo_m...\', (3, 3):'
			" Published tables need an assigned id.",
			base.parseFromString,
			(rscdesc.RD, """<resource schema="data">
			<table><register sets="ivo_managed,local"/>
			</table></resource>"""))

	def testDataPublicationPurged(self):
		# this is actually a companion to DataPublicationTest making sure
		# that _PublishedData's clean method has worked, possibly last time.
		# Sorry 'bout that funkyness, but this is tricky to do sanely.
		self.assertEqual(len(list(
			self.conn.query("SELECT * FROM dc.resources where sourcerd=%(rdId)s",
				{"rdId": _PublishedData.rdId}))), 0,
				"registrytest._PublishedData.clean failed?")
		self.assertEqual(
			common.getDependencies("__system__/services", connection=self.conn),
			[],
			"registrytest._PublishedData.clean failed?")

	def testContentLevelChecked(self):
		rec = base.parseFromString(nonservice.ResRec,
			f"""<resRec>
				{tresc.MINIMAL_META}
				<meta name="referenceURL">b</meta>
				<meta>
				resType: document
				contentLevel: University
				</meta></resRec>""")
		self.assertRaisesWithMsg(
			base.MetaValidationError,
			"Meta structure on ResRec item (within IO:'<resRec> <meta> title:x creationDate:2020-03-05T12:13...', line 1) did not validate: contentLevel meta only admits values from voresource/content_level, but 'University' is not in there.",
			base.validateStructure,
			(rec,))


class _ResobGrammarResponse(testhelpers.TestResource):
	"""A resource giving the records the grammar in registry.publication
	spits out for a simple resource.
	"""
	resources = [("resg", _resourceGrammar)]

	def make(self, deps):
		tables = {}
		rd = base.caches.getRD("data/ssatest")
		for destTable, row in deps["resg"].parse(rd):
			del row["parser_"]
			tables.setdefault(destTable, []).append(row)
		return tables


class ResobGrammarTest(testhelpers.VerboseTest):
	resources = [("tables", _ResobGrammarResponse())]

	def testServicePresent(self):
		self.assertEqual(self.tables["resources"][0]["ivoid"],
			'ivo://x-testing/data/ssatest/s')
	
	def testSubjects(self):
		self.assertEqual([r for r in self.tables["subjects"]
				if r["sourceRD"]!="__system__/tap"], [
			{'sourceRD': 'data/ssatest', 'resId': 's', 'subject': 'testing'},
			{'sourceRD': 'data/ssatest', 'resId': 'c', 'subject': 'testing'},
			{'resId': 'hcdtest', 'sourceRD': 'data/ssatest', 'subject': 'testing'}])
	
	def testAuthors(self):
		self.assertEqual(
			set([r["author"] for r in self.tables["authors"]]),
			set(['Hendrix, J.', 'Page, J', 'The Master Tester']))


class _PublishedRD(testhelpers.TestResource):
	"""A resource that publishes all the stuff from an RD for while the
	resource exists.

	The RD to be published is given in the rdId class attribute.
	"""
	resources = [("conn", tresc.dbConnection)]

	def make(self, deps):
		self.conn = deps["conn"]
		rd = base.caches.getRD(self.rdId)
		publication.updateServiceList([rd], connection=self.conn)
		return rd
	
	def clean(self, res):
		publication._purgeFromServiceTables(self.rdId, self.conn)
		self.conn.commit()


class _PublishedData(_PublishedRD):
	rdId = "data/testdata"


class DataPublicationTest(testhelpers.VerboseTest):
# Tests for a published table
	resources = [
		("conn", tresc.dbConnection),
		("pubDataRD", _PublishedData())]

	def testPublication(self):
		self.assertEqual(len(list(
			self.conn.query("SELECT * FROM dc.resources where sourcerd=%(rdId)s",
				{"rdId": self.pubDataRD.sourceId}))), 1)

	def testAuthorExpanded(self):
		self.assertEqual(next(
			self.conn.query("SELECT author FROM dc.authors"
				" WHERE sourcerd='data/testdata' AND resid='barsobal'"))[0],
				"Your organisation's name")

	def testDescriptionExpanded(self):
		self.assertEqual(next(
			self.conn.query("SELECT description FROM dc.resources"
				" WHERE sourcerd='data/testdata' AND resid='barsobal'"))[0],
				"It's expanded!")

	def testResobGeneration(self):
		td = self.pubDataRD.getById("barsobal")
		ivoId = base.getMetaText(td, "identifier")
		resOb = registry.getResobFromIdentifier(ivoId)
		self.assertEqual(td, resOb)
	
	def testIsInDependencies(self):
		self.assertEqual(
			registry.getDependencies("__system__/services", connection=self.conn),
			["data/testdata"])


class _DataVORRecord(testhelpers.TestResource):
	def make(self, ignored):
		res = base.caches.getRD("data/testdata")
		tree = testhelpers.getXMLTree(
			builders.getVOResourceElement(res.getById("barsobal"
				)).render(), debug=False)
		return tree.xpath("metadata/Resource")[0]
	

class DataVORTest(testhelpers.VerboseTest):
	resources = [("tree", _DataVORRecord())]

	def testPlainInstrument(self):
		self.assertEqual(
			self.tree.xpath("instrument")[0].text,
			"Instrument without identity")

	def testRegisteredInstrument(self):
		self.assertEqual(
			self.tree.xpath("instrument")[1].text,
			"Instrument with ivoid")
		self.assertEqual(
			self.tree.xpath("instrument/@ivo-id")[0],
			"ivo://x-example/inst1")

	def testPIDInstrument(self):
		self.assertEqual(
			self.tree.xpath("instrument")[2].text,
			"Instrument with ivoid and doi")
		self.assertEqual(
			self.tree.xpath("instrument/@ivo-id")[1],
			"ivo://x-example/inst2")
		self.assertEqual(
			self.tree.xpath("instrument/@altIdentifier")[0],
			"doi:107331/fakefake")

	def testDataSource(self):
		self.assertEqual(
			[n.text for n in self.tree.xpath("dataSource")],
			["theory", "observation"])


class _ServiceVORRecord(testhelpers.TestResource):
	def make(self, ignored):
		rd = base.parseFromString(rscdesc.RD, r"""<resource schema="data">
			<meta name="creationDate">2011-03-04T11:00:00</meta>
			<meta name="title">A senseless service</meta>
			<meta name="source">1989AGAb....2...33W</meta>
			<meta name="type">Other</meta>
			<meta name="type">Simulation</meta>
			<meta name="_example" title="ex1">No text</meta>
			<meta name="_example" title="ex2">Even less</meta>
			<meta name="creator">\upper{f.f.f}</meta>
			<FEED source="//procs#license-cc-by" what="senseless data"/>
			<meta name="rights">Licensed CC-BY</meta>
			<meta name="rights.rightsURI"
				>http://creativecommons.org/licenses/by/3.0/</meta>
			<meta name="productType">spectrum</meta>
			<meta name="productType">timeseries</meta>
			<coverage>
				<spatial>2/12-14</spatial>
			</coverage>
			<table id="data" onDisk="True" adql="True">
				<mixin>//obscore#publishSSAPMIXC</mixin>
				<publish/>
				<column name="score"/>
			</table>
			<service id="glonk">
				<meta name="ssap.dataSource">pointed</meta>
				<meta name="ssap.testQuery">MAXREC=1</meta>
				<dbCore queriedTable="data"/>
				<publish render="form" sets="ivo_managed,local"/>
				<publish render="ssap.xml" sets="ivo_managed"/>
			</service></resource>""")
		rd.sourceId = "funky/town"
		base.caches.getRD.cacheCopy["funky/town"] = rd
		tree = testhelpers.getXMLTree(
			builders.getVOResourceElement(rd.services[0]).render(), debug=False)
		return tree.xpath("metadata/Resource")[0]

_serviceVORRecord = _ServiceVORRecord()


class ServiceRecordTest(testhelpers.VerboseTest):
	resources = [("rec", _serviceVORRecord)]

	def testVersion(self):
		self.assertEqual(self.rec.get("version"), "1.2")

	def testSourceFormatInferred(self):
		self.assertEqual(self.rec.xpath("content/source")[0].get("format"),
			"bibcode")
	
	def testContentTypePresent(self):
		self.assertEqual(self.rec.xpath("content/type")[0].text,
			"Other")
		self.assertEqual(self.rec.xpath("content/type")[1].text,
			"Simulation")
	
	def testExamplesCap(self):
		cap = self.rec.xpath("//capability[@standardID='ivo://ivoa.net/std"
			"/DALI#examples']")[0]
		self.assertEqual(cap.xpath("interface")[0].get(
			"{http://www.w3.org/2001/XMLSchema-instance}type"),
			"vr:WebBrowser")
		self.assertTrue(cap.xpath("interface/accessURL")[0].text.endswith(
			"/funky/town/glonk/examples"))

	def testRights(self):
		node = self.rec.xpath("//Resource/rights")[0]
		self.assertEqual(node.text.strip(), "senseless data is licensed under the"
			" `Creative Commons Attribution 4.0 License <http://creativecommons."
			"org/licenses/by/4.0/>`_\n\n.. image:: /static/img/ccby.png"
			"\n   :alt: [CC-BY]")
		self.assertEqual(node.get("rightsURI"),
			"https://spdx.org/licenses/CC-BY-4.0.html")

	def testFootprint(self):
		node = self.rec.xpath("//coverage/footprint")[0]
		self.assertEqual(node.text,
			"http://localhost:8080/funky/town/glonk/coverage")
		self.assertEqual(node.get("ivo-id"), "ivo://ivoa.net/std/moc")

	def testUniqueTAPAux(self):
		auxCaps = self.rec.xpath("capability[@standardID="
			"'ivo://ivoa.net/std/TAP#aux']")
		self.assertEqual(len(auxCaps), 1)
	
	def testObscoreDecl(self):
		self.assertEqual(len(self.rec.xpath("tableset/schema[name='ivoa']"
			"/table[name='ivoa.obscore']")), 1)

	def testAuthor(self):
		self.assertEqual(self.rec.xpath("curation/creator/name")[0].text,
			"F.F.F")


class _UWSVORRecord(testhelpers.TestResource):
	def make(self, ignored):
		tree = testhelpers.getXMLTree(
			builders.getVOResourceElement(base.resolveCrossId("data/cores#pc")
				).render(), debug=False)
		return tree.xpath("metadata/Resource")[0]

_uwsVORRecord = _UWSVORRecord()


class UWSRecordTest(testhelpers.VerboseTest):
	resources = [("rec", _uwsVORRecord)]

	def testFileDeclaration(self):
		fileParam = self.rec.xpath("//param[name='UPLOAD']")[0]
		dataType = fileParam.xpath("dataType")[0]
		self.assertEqual(dataType.text, "char")
		self.assertEqual(dataType.get("arraysize"), "*")

	def testVOSIInterfaceStandard(self):
		capcap = self.rec.xpath(
			"//capability[@standardID='ivo://ivoa.net/std/VOSI#capabilities']")[0]
		self.assertEqual(capcap.xpath("interface")[0].get("role"), "std")


class _TableVORRecord(testhelpers.TestResource):
	def make(self, ignored):
		rd = base.parseFromString(rscdesc.RD, """<resource schema="data">
			<meta name="creationDate">2011-03-04T11:00:00</meta>
			<meta name="title">My first DataCollection</meta>
			<meta name="utype">http://example.edu/purefantasy</meta>
			<meta name="description">Just so there's something here</meta>
			<coverage>
				<spectral>3.4213e-07 8.6328e-07</spectral>
				<temporal>54992.06442 54992.35</temporal>
				<temporal>54993.02 54993.42</temporal>
				<temporal>1999-02-01T12:00:00 1999-03-15T00:00:00</temporal>
				<spatial>6/20764,22787,38016,41738</spatial>
			</coverage>
			<meta name="coverage">
				<meta name="profile">Box ICRS 12 13 2 3</meta>
				<meta name="waveband">X-Ray</meta>
				<meta name="waveband">Radio</meta>
				<meta name="regionOfRegard">3</meta>
			</meta>

			<table id="punk" onDisk="True" adql="True">
				<column name="oink" utype="noises:animal.pig"/>
				<column name="whereitsat" type="spoint" ucd="pos.eq;source"/>
				<publish sets="ivo_managed,local"
					services="//tap#run,data/pubtest#moribund">
					<meta name="mirrorURL">http://localhost:8080/tap</meta>
					<meta name="mirrorURL">http://whereever.el.se/overview</meta>
				</publish>					
				<meta name="utype">testing.table.name</meta>
				<meta name="description">Some silly test data</meta>
				<meta name="subject">testing</meta>
				<meta name="subject">regressions</meta>
				<meta name="format">audio/vorbis</meta>
				<meta name="referenceURL">http://junk.g-vo.org</meta>
				<meta name="servedBy" ivoId="ivo://org.g-vo.junk/tap"
					>GAVO TAP service</meta>
				<meta name="servedBy" ivoId="ivo://org.g-vo.junk/adql"
					>GAVO ADQL Web</meta>
				<meta name="continues" ivoId="ivo://org.g-vo.junk/zero"
					>My Zeroeth Data Collection</meta>
			</table></resource>""")
		rd.sourceId = "funky/town"
		td = rd.tables[0]
		tree = testhelpers.getXMLTree(
			builders.getVOResourceElement(td).render(), debug=False)
		return tree.xpath("metadata/Resource")[0]

_tableVORRecord = _TableVORRecord()


class TablePublicationRecordTest(testhelpers.VerboseTest):
# Tests for the registry record of a data publication
	resources = [("tree", _tableVORRecord)]

	def testCreatedInherited(self):
		self.assertEqual(self.tree.attrib["created"], "2011-03-04T11:00:00Z")
	
	def testConfigMetaPresent(self):
		self.assertEqual(
			self.tree.xpath("curation/contact/email")[0].text,
			base.getMetaText(meta.CONFIG_META, "contact.email"))
	
	def testVORModelWorked(self):
		self.assertEqual(
			self.tree.xpath("content/description")[0].text,
			"Some silly test data")

	def testVORModelWorked2(self):
		self.assertEqual(self.tree.xpath("title")[0].text,
			"My first DataCollection")

	def testAllSubjectsRendered(self):
		self.assertEqual(len(self.tree.xpath("content/subject")), 2)
	
	def testCoverageProfileRendered(self):
		self.assertEqual(self.tree.xpath(
			"coverage/STCResourceProfile/AstroCoordArea/Box/Size/C1")[0].text,
			"2.0")

	def testWavebandsPresent(self):
		bands = self.tree.xpath("coverage/waveband")
		self.assertEqual(len(bands), 2)
		self.assertEqual(bands[0].text, "X-Ray")

	def testSpatialCoverage(self):
		self.assertEqual(
			self.tree.xpath("coverage/spatial")[0].text,
			"6/20764,22787,38016,41738")
	
	def testTemporalCoverage(self):
		covs = self.tree.xpath("coverage/temporal")
		self.assertEqual(len(covs), 3)
		self.assertEqual(covs[0].text, "54992.06442 54992.35")
		self.assertEqual(covs[1].text, "54993.02 54993.42")
		self.assertEqual(covs[2].text, "51210.5 51252")
	
	def testTemporalSpectral(self):
		cov = self.tree.xpath("coverage/spectral")[0]
		self.assertEqual(cov.text, "3.4213e-07 8.6328e-07")

	def testFootprintURL(self):
		# tables can't have renderers on them, so they mustn't have
		# a footprint URL
		fp = self.tree.xpath("coverage/footprint")
		self.assertFalse(fp, "Table has coverage/footprint??")

	def testRegionOfRegardPresent(self):
		self.assertEqual(self.tree.xpath("coverage/regionOfRegard")[0].text,
			"3")

	def testTablesetRendered(self):
		self.assertEqual(self.tree.xpath("tableset/schema/table/name")[0].text,
			"data.punk")

	def testSchemaUtypeInTableset(self):
		self.assertEqual(self.tree.xpath("tableset/schema/utype")[0].text,
			"http://example.edu/purefantasy")

	def testSchemaDescriptionInTableset(self):
		self.assertEqual(self.tree.xpath("tableset/schema/description")[0].text,
			"Just so there's something here")

	def testColumnMetaRendered(self):
		self.assertEqual(
			self.tree.xpath("tableset/schema/table/column")[0
				].xpath("name")[0].text,
			"oink")

	def testRelationship(self):
		par = self.tree.xpath("//relationship")[0]
		self.assertEqual(par.xpath("relationshipType")[0].text, "IsServedBy")
		self.assertEqual(par.xpath("relatedResource")[0].text,
			"GAVO TAP service")
		self.assertEqual(par.xpath("relatedResource")[0].attrib["ivo-id"],
			"ivo://org.g-vo.junk/tap")
		self.assertEqual(par.xpath("relatedResource")[1].attrib["ivo-id"],
			"ivo://org.g-vo.junk/adql")

	def testContinues(self):
		par = self.tree.xpath("//relationship")[1]
		self.assertEqual(par.xpath("relationshipType")[0].text, "Continues")
		self.assertEqual(par.xpath("relatedResource")[0].text,
			"My Zeroeth Data Collection")
		self.assertEqual(par.xpath("relatedResource")[0].attrib["ivo-id"],
			"ivo://org.g-vo.junk/zero")

	def testUtype(self):
		self.assertEqual(self.tree.xpath("tableset/schema/table/utype")[0].text,
			"testing.table.name")

	def testTAPCapabilityPresent(self):
		auxCaps = self.tree.xpath(
			"capability[@standardID='ivo://ivoa.net/std/TAP#aux']")
		auxCap = testhelpers.pickSingle(auxCaps)
		self.assertEqual(auxCap.xpath("interface[@role='std']/accessURL")[0].text,
			"http://localhost:8080/tap")

	def _testWebCapabilityPresent(self):
		# that's currently not happening, as we don't create anonymous
		# capabilites for data sets.  Solution?
		self.assertEqual(self.tree.xpath(
			"//accessURL[.='http://localhost:8080/data/pubtest/moribund/form']")[0].
			get("use"), "full")

	def testVOSICapabilityCensored(self):
		self.assertEqual(self.tree.xpath(
			"//capability[@standardID='ivo://ivoa.net/std/VOSI#availability']"),
			[])
	
	def testMirrorURL(self):
		mirrors = self.tree.xpath("//mirrorURL")
		self.assertEqual(
			testhelpers.pickSingle(mirrors).text,
			"http://whereever.el.se/overview")

	def testXtypeRendered(self):
		dt = self.tree.xpath("//column[name='whereitsat']/dataType")[0]
		self.assertEqual(dt.get("extendedType"), "point")
		self.assertEqual(dt.get("arraysize"), "2")


class _DataGetRecordRes(testhelpers.TestResource):
	def make(self, ignored):
		rd = base.parseFromString(rscdesc.RD, """<resource schema="data">
			<meta name="creationDate">2011-03-04T11:00:00</meta>
			<meta name="title">My second DataCollection</meta>
			<meta name="utype">ivo://some.mad.stuff/datamodel#schema-2.0</meta>
			<meta name="creator.name">M.Y. Logo</meta>
			<meta name="creator.logo">/static/mylogo.png</meta>
			<table id="honk" onDisk="True" temporary="True" adql="True">
				<column name="col1" description="column from honk"/>
			</table>
			<table id="funk" onDisk="True" temporary="True" adql="True">
				<column name="oink" utype="noises:animal.pig"/>
				<column name="wherep" type="spoint" ucd="pos.eq;source"/>
			</table>
			<data id="punk">
				<register sets="ivo_managed,local"/>
				<meta name="description">Some silly test data</meta>
				<meta name="subject">testing</meta>
				<meta name="subject">regressions</meta>
				<meta name="coverage.profile">Box ICRS 12 13 2 3</meta>
				<meta name="format">audio/vorbis</meta>
				<meta name="referenceURL">http://junk.g-vo.org</meta>
				<make table="honk"/>
				<make table="funk"/>
			</data></resource>""")
		rd.sourceId = "funky/town"
		return getGetRecordResponse(rd.dds[0])

_dataGetRecordRes = _DataGetRecordRes()


class DataGetRecordTest(testhelpers.VerboseTest, testtricks.XSDTestMixin):
	resources = [("srcAndTree", _dataGetRecordRes)]

	def testIsValid(self):
		self.assertValidates(self.srcAndTree[0])

	def testType(self):
		self.assertEqual(self.srcAndTree[1].xpath("//Resource")[0].get(
			"{http://www.w3.org/2001/XMLSchema-instance}type"),
			"vs:CatalogResource")

	def testAutoTAPCapabilityPresent(self):
		self.assertEqual(self.srcAndTree[1].xpath(
			"//capability[@standardID='ivo://ivoa.net/std/TAP#aux']"
			"/interface[@role='std']/accessURL")[0].text,
			"http://localhost:8080/tap")

	def testReducedCapabilityElement(self):
		capChildren = self.srcAndTree[1].xpath("//capability/*")
		self.assertEqual(len(capChildren), 1)
	
	def testUtypeDeclared(self):
		utypeChildren = self.srcAndTree[1].xpath("//schema/utype")
		self.assertEqual(testhelpers.pickSingle(utypeChildren).text,
			"ivo://some.mad.stuff/datamodel#schema-2.0")

	def testTablesetPresent(self):
		tables = self.srcAndTree[1].xpath(
			"//tableset/schema/table")
		self.assertEqual(
			set([
				tables[0].xpath("name")[0].text,
				tables[1].xpath("name")[0].text]),
			set(["funk", "honk"]))


class _BiblinkRecordRes(testhelpers.TestResource):
	def make(self, ignored):
		tree = testhelpers.getXMLTree(
			builders.getVOResourceElement(
				base.resolveCrossId("//biblinks#links")).render(), debug=False)
		return tree.xpath("metadata/Resource")[0]

class BiblinkRecordTest(testhelpers.VerboseTest):
	resources = [("tree", _BiblinkRecordRes())]

	def testCapability(self):
		caps = self.tree.xpath("//capability[@standardID="
			"'ivo://ivoa.net/std/BibVO#biblink-harvest-1.0']")
		self.assertEqual(
			testhelpers.pickSingle(caps).xpath("interface")[0].get(
				"{http://www.w3.org/2001/XMLSchema-instance}type"),
			"vs:ParamHTTP")


class _VOEventRecord(testhelpers.TestResource):
	def make(self, deps):
		rd = base.parseFromString(rscdesc.RD, """
				<resource schema="data">
			<meta>
				shortName: chime_voe
				creationDate: 2025-04-02T07:00:00Z
				title: CHIME/FRB VOEvent
				subject:time-domain-astronomy
				description: Test.
				date: 2040-01-01T00:00:00
				date.role: updated
			</meta>

			<service id="s" allowed="voevent">
				<nullCore/>
				<publish render="voevent" sets="ivo_managed">
					<meta name="accessURL">x-vtp://chimefrb.physics.mcgill.ca</meta>
					<meta name="interfaceType">VTPInterface</meta>
				</publish>
			</service>
		</resource>""")
		rd.sourceId = "voevent/test"
		return getGetRecordResponse(rd.getById("s"))


class VOEventRecordTest(testhelpers.VerboseTest, testhelpers.XSDTestMixin):

	resources = [("textAndTree", _VOEventRecord())]

	def testValid(self):
		self.assertValidates(self.textAndTree[0])
	
	def testVOEventInterface(self):
		cap = testhelpers.pickSingle(self.textAndTree[1].xpath("//capability["
			"@standardID='ivo://ivoa.net/std/voevent']"))
		self.assertEqual(cap.get("standardID"), "ivo://ivoa.net/std/voevent")
		intf = testhelpers.pickSingle(
			cap.xpath("interface"))
		self.assertEqual(intf.get("role"), "std")
		self.assertEqual(
			intf.get("standardID"), "ivo://ivoa.net/std/voevent#acc-vtp")
		self.assertEqual(
			intf.get("{http://www.w3.org/2001/XMLSchema-instance}type"),
			"voe:StreamEndpoint")
		self.assertEqual(
			testhelpers.pickSingle(intf.xpath("accessURL")).text,
			"x-vtp://chimefrb.physics.mcgill.ca")

	def testCapabilitiesInterface(self):
		cap = testhelpers.pickSingle(self.textAndTree[1].xpath("//capability["
			"@standardID='ivo://ivoa.net/std/VOSI#capabilities']"))
		intf = testhelpers.pickSingle(
			cap.xpath("interface"))
		self.assertEqual(
			testhelpers.pickSingle(intf.xpath("accessURL")).text,
			"http://localhost:8080/voevent/test/s/capabilities")


class SpecialMetaRenderTest(testhelpers.VerboseTest):
	resources = [("srcAndTree", _dataGetRecordRes)]

	def testLogoExpanded(self):
		self.assertEqual(
			self.srcAndTree[1].uniqueXpath("//creator/logo").text,
			"http://localhost:8080/static/mylogo.png")


_NMDB_SVC_LIT = """
<resource schema="gvo">
	<meta name="title">NMDB: Real-time Measurements</meta>
	<meta name="description">NMDB serves the data</meta>
	<meta name="creationDate">2015-09-17T11:45:00</meta>
	<meta name="subject">Cosmic ray</meta>
	<meta name="creator.name">Steigies, C.</meta>
	<meta name="creator">
		<meta name="name">Wilbanks, J.
		<meta name="altIdentifier">http://orcid.org/0000-0002-4510-0385</meta>
		</meta>
	</meta>
	<meta name="contributor">
		<meta name="altIdentifier">http://orcid.org/blabla</meta>Joe Cool
	</meta>
	<service id="run" allowed="external">
		<nullCore/>
		<meta name="shortName">nmdb web</meta>
		<publish render="external" sets="ivo_managed">
		<meta
			name="accessURL">http://nest.nmdb.eu</meta>
		</publish>
	</service>
</resource>
"""


class _ExternalRecordRes(testhelpers.TestResource):
	def make(self, ignored):
		rd = base.parseFromString(rscdesc.RD, _NMDB_SVC_LIT)
		rd.sourceId = "funky/town"
		return getGetRecordResponse(rd.services[0])


class ExternalRecordTest(testhelpers.VerboseTest, testtricks.XSDTestMixin):
	resources = [("srcAndTree", _ExternalRecordRes())]

	def testWebInterface(self):
		intf = self.srcAndTree[1].xpath("//capability/interface")[0]
		self.assertEqual(intf.xpath("accessURL")[0].text, "http://nest.nmdb.eu")
	
	def testNoTablesEndpoint(self):
		self.assertEqual(
			list(self.srcAndTree[1].xpath("//capability/@standardID")),
			['ivo://ivoa.net/std/VOSI#capabilities'])

	def testValid(self):
		self.assertValidates(self.srcAndTree[0])

	def testCreatorAltIdentifier(self):
		self.assertEqual(
			self.srcAndTree[1].xpath("//creator/name/@altIdentifier")[0],
			"http://orcid.org/0000-0002-4510-0385")

	def testContributorAltIdentifier(self):
		self.assertEqual(
			self.srcAndTree[1].xpath("//contributor/@altIdentifier")[0],
			"http://orcid.org/blabla")


class ForeignIdentifiersRejectedTest(testhelpers.VerboseTest):
	def testExternalIdsSuppressed(self):
		svcMat = re.search("(?s)<service.*?</service>", _NMDB_SVC_LIT)
		foreignService = re.sub("<service.*?>",
			'<service><meta name="identifier">ivo://ivoa.net/foreign</meta>',
			svcMat.group(0))
		rdLit = _NMDB_SVC_LIT[:svcMat.end()
			]+foreignService+_NMDB_SVC_LIT[svcMat.end():]
		rd = base.parseFromString(rscdesc.RD, rdLit)
		rd.sourceId = "funky/town"

		grammar = base.makeStruct(publication.RDRscRecGrammar)
		res = [p for (d, p) in grammar.parse(rd) if d=='resources']
		self.assertEqual(
			testhelpers.pickSingle(res)["ivoid"],
			'ivo://x-testing/funky/town/run')

	def testXUnregisteredForLocalIsOk(self):
		base.caches.clearForName("data/test")
		# test.rd doesn't have any ivo_managed resources (and must not
		# grow any or this will break)
		try:
			with testhelpers.tempConfig(("ivoa", "authority", "x-unregistred")):
				publication.updateServiceList([base.resolveCrossId("data/test")])
		finally:
			base.caches.clearForName("data/test")
			publication.updateServiceList(
				[base.resolveCrossId("data/test")],
				unpublish=True)

	def testXUnregisteredFails(self):
		base.caches.clearForName("data/seeded")
		with testhelpers.tempConfig(("ivoa", "authority", "x-unregistred")):
			self.assertRaisesWithMsg(base.ReportableError,
				"Resource data/seeded#hooray uses the authority"
				" x-unregistered; this is not permitted.",
				publication.updateServiceList,
				([base.resolveCrossId("data/seeded")],))
		base.caches.clearForName("data/seeded")


class _ProductServiceRecord(testhelpers.TestResource):
	def make(self, ignored):
		return getGetRecordResponse(api.getRD("//products").getById("p"))


class ProductServiceTest(testhelpers.VerboseTest):
	resources = [("srcAndTree", _ProductServiceRecord())]

	def testIdentifier(self):
		self.assertEqual(
			self.srcAndTree[1].xpath("//identifier")[0].text,
			"ivo://x-testing/~")
	
	def testCreationDate(self):
		self.assertEqual(
			self.srcAndTree[1].xpath("//Resource")[0].get("created"),
			base.getMetaText(base.MetaMixin(), "authority.creationDate")+"Z")

	def testDescriptionExpanded(self):
		self.assertTrue("datasets held at\nUnittest Suite" in
			self.srcAndTree[1].xpath("//content/description")[0].text,
			"Product service description doesn't contain site name?")

	def testGetCapability(self):
		el = self.srcAndTree[1].xpath(
			"//interface[@xsi:type='vr:WebBrowser']/accessURL",
			namespaces={"xsi": utils.getPrefixInfo("xsi")[0]})[0]
		self.assertEqual(el.text, "http://localhost:8080/getproduct")
		self.assertEqual(el.get("use"), "base")

	def testDLCapability(self):
		el = self.srcAndTree[1].xpath(
			"//capability[@standardID='ivo://ivoa.net/std/DataLink#links-1.1']")[0]
		self.assertEqual(el.xpath("interface/accessURL")[0].text,
			"http://localhost:8080/__system__/products/dl/dlmeta")
		self.assertEqual(el.xpath("interface")[0].get("role"),
			"std"),
		self.assertEqual(el.xpath("interface/param[name='ID']")[0].get("std"),
			"true")


class _WithDatalinkRecord(testhelpers.TestResource):
	def make(self, ignored):
		return getGetRecordResponse(api.resolveCrossId("data/ssatest#c"))


class WithDatalinkTest(testhelpers.VerboseTest):
	resources = [("srcAndTree", _WithDatalinkRecord())]

	def testSODACapPresent(self):
		sodaCaps = self.srcAndTree[1].xpath(
			"//capability[@standardID='ivo://ivoa.net/std/SODA#sync-1.0']")
		self.assertEqual(len(sodaCaps), 1)

	def testSODAAccessURL(self):
		dlURLs = self.srcAndTree[1].xpath(
			"//capability[@standardID='ivo://ivoa.net/std/SODA#sync-1.0']"
			"/interface/accessURL")
		self.assertTrue(
			testhelpers.pickSingle(dlURLs).text.endswith("/data/ssatest/dl/dlget"))

	def testSODAParameters(self):
		parentPath = ("//capability[@standardID="
			"'ivo://ivoa.net/std/SODA#sync-1.0']/interface")
		intfEl = self.srcAndTree[1].xpath(parentPath)[0]
		# can't compute them without data, so make sure we don't
		# invent something.
		self.assertEqual(len(intfEl.xpath("param")), 0)

	def testDatalinkCapPresent(self):
		dlCaps = self.srcAndTree[1].xpath(
			"//capability[@standardID='ivo://ivoa.net/std/DataLink#links-1.1']")
		self.assertEqual(len(dlCaps), 1)
	
	def testDatalinkInterfaceDeclared(self):
		dlIntfs = self.srcAndTree[1].xpath(
			"//capability[@standardID='ivo://ivoa.net/std/DataLink#links-1.1']"
			"/interface")
		self.assertEqual(len(dlIntfs), 1)
		self.assertEqual(dlIntfs[0].get(
			"{http://www.w3.org/2001/XMLSchema-instance}type"), 'vs:ParamHTTP')

	def testDatalinkAccessURL(self):
		dlURLs = self.srcAndTree[1].xpath(
			"//capability[@standardID='ivo://ivoa.net/std/DataLink#links-1.1']"
			"/interface/accessURL")
		self.assertTrue(
			testhelpers.pickSingle(dlURLs).text.endswith("/data/ssatest/dl/dlmeta"))

	def testDatalinkResultType(self):
		dlTypes = self.srcAndTree[1].xpath(
			"//capability[@standardID='ivo://ivoa.net/std/DataLink#links-1.1']"
			"/interface/resultType")
		self.assertEqual(testhelpers.pickSingle(dlTypes).text,
			"application/x-votable+xml;content=datalink")

	def testDatalinkParameters(self):
		dlParams = self.srcAndTree[1].xpath(
			"//capability[@standardID='ivo://ivoa.net/std/DataLink#links-1.1']"
			"/interface/param[@std='true']")
		self.assertEqual(len(dlParams), 4)
		self.assertEqual(dlParams[0][0].tag, "name")
		self.assertEqual(dlParams[0][0].text, "ID")
		self.assertEqual(dlParams[0][2].tag, "ucd")
		self.assertEqual(dlParams[0][2].text, "meta.id;meta.main")


_PUBLISHED_RD_LIT_FRAME = f"""<resource schema="test">
{tresc.MINIMAL_META}

<meta name="!creator">Zwicky, F.; Schmidt, R.</meta>

<publish sets="local,ivo_managed"/>

{{}}

</resource>"""

_PUBLISHED_RD_LIT_CONTENT = """
<table id="ispub" onDisk="True" adql="True">
	<column name="col1"/>
</table>

<table id="nonpub" onDisk="True">
	<column name="col2"/>
</table>

<table id="hidden" onDisk="True" adql="hidden">
	<column name="col3"/>
</table>

<service id="svc1" allowed="form,scs.xml">
	<meta>
		shortName: svc1
		testQuery.ra: 10
		testQuery.dec: 10
		testQuery.sr: 1
	</meta>
	<dbCore queriedTable="ispub"/>
</service>

<service id="svc2" allowed="hips">
	<meta name="shortName">svc2</meta>
	<meta name="description">The second service</meta>
	<property name="staticData">data</property>
	<nullCore/>
</service>
"""


_PUBLISHED_RD_LIT = _PUBLISHED_RD_LIT_FRAME.format(_PUBLISHED_RD_LIT_CONTENT)


class _RDPubRecs(testhelpers.TestResource):
	def make(self, ignored):
		rd = base.parseFromString(api.RD, _PUBLISHED_RD_LIT)
		rd.sourceId = "test/fake"
		grammar = base.makeStruct(publication.RDRscRecGrammar)
		recs = {}
		for dest, row in grammar.parse(rd):
			recs.setdefault(dest, []).append(row)
		return recs


class RDPubDBTest(testhelpers.VerboseTest):
	resources = [("recs", _RDPubRecs())]

	def testResource(self):
		rec = testhelpers.pickSingle(self.recs["resources"])
		self.assertEqual(rec["ivoid"], 'ivo://x-testing/test/fake')
		self.assertEqual(rec["shortName"], 'test/fake')
		self.assertEqual(rec["resId"], 'WHOLE_RD')
		self.assertEqual(rec["authors"], 'Zwicky, F.; Schmidt, R.')
		self.assertEqual(rec["dateUpdated"], None)
	
	def testSets(self):
		recs = self.recs["sets"]
		self.assertEqual(len(recs), 2)
		self.assertEqual(recs[0]["ivoid"], 'ivo://x-testing/test/fake')
		self.assertEqual(recs[1]["sourceRD"], 'test/fake')
		self.assertEqual(recs[1]["renderer"], 'rcdisplay')
		self.assertEqual({r["setName"] for r in recs}, {'ivo_managed', 'local'})
	
	def testSubjects(self):
		rec = testhelpers.pickSingle(self.recs["subjects"])
		self.assertEqual(rec["sourceRD"], 'test/fake')
		self.assertEqual(rec["subject"], 'a')

	def testAuthors(self):
		recs = self.recs["authors"]
		self.assertEqual(len(recs), 2)
		self.assertEqual(recs[0]["author"], "Zwicky, F.")
		self.assertEqual(recs[0]["sourceRD"], 'test/fake')

	def testInterface(self):
		rec = testhelpers.pickSingle(self.recs["interfaces"])
		self.assertEqual(rec["sourceRD"], 'test/fake')
		self.assertEqual(rec["resId"], 'WHOLE_RD')
		self.assertEqual(rec['accessURL'],
			'http://localhost:8080/browse/test/fake')
		self.assertEqual(rec['referenceURL'],
			'http://localhost:8080/browse/test/fake')


class _RDVOR(testhelpers.TestResource):
	def make(self, ignored):
		rd = base.parseFromString(api.RD, _PUBLISHED_RD_LIT)
		rd.sourceId = "test/fake"
		return getGetRecordResponse(rd)


class RDPubVORTest(testhelpers.VerboseTest, testtricks.XSDTestMixin):
	resources = [("textAndTree", _RDVOR())]

	def testValid(self):
		self.assertValidates(self.textAndTree[0])

	def testIdentifier(self):
		self.assertEqual(
			testhelpers.pickSingle(
				self.textAndTree[1].xpath("GetRecord/record/metadata/"
					"Resource/identifier")).text,
			"ivo://x-testing/test/fake")

	def testSCSCap(self):
		cap = testhelpers.pickSingle(
			self.textAndTree[1].xpath(
				"//capability[@standardID='ivo://ivoa.net/std/conesearch']"))
		self.assertEqual(testhelpers.pickSingle(
				cap.xpath("interface/accessURL")).text,
			"http://localhost:8080/test/fake/svc1/scs.xml?")

	def testBrowserCap(self):
		cap = testhelpers.pickSingle(
			self.textAndTree[1].xpath(
				"//capability[interface/@xsi:type='vr:WebBrowser']",
				namespaces={"xsi": utils.getPrefixInfo("xsi")[0]}))
		self.assertEqual(testhelpers.pickSingle(
				cap.xpath("interface/accessURL")).text,
			"http://localhost:8080/test/fake/svc1/form")

	def testHIPSCap(self):
		cap = testhelpers.pickSingle(
			self.textAndTree[1].xpath(
				"//capability[@standardID='ivo://ivoa.net/std/hips#hips-1.0']"))
		self.assertEqual(
			testhelpers.pickSingle(cap.xpath("interface/accessURL")).text,
			"http://localhost:8080/test/fake/svc2/hips")
		self.assertEqual(
			testhelpers.pickSingle(cap.xpath("description")).text,
			"The second service")

	def testTAPaux(self):
		cap = testhelpers.pickSingle(
			self.textAndTree[1].xpath(
				"//capability[@standardID='ivo://ivoa.net/std/TAP#aux']"))
		self.assertEqual(testhelpers.pickSingle(
				cap.xpath("interface/accessURL")).text,
			"http://localhost:8080/tap")

	def testTableset(self):
		ts = testhelpers.pickSingle(self.textAndTree[1].xpath("//tableset"))
		self.assertEqual(
			testhelpers.pickSingle(ts.xpath("schema/table/name")).text,
			"test.ispub")


class _ExoticRDVOR(testhelpers.TestResource):
	def make(self, ignored):
		rd = base.parseFromString(api.RD, _PUBLISHED_RD_LIT_FRAME.format(
			"""
				<table id="something">
					<column name="other"/>
				</table>
				<service id="s" allowed="form,static">
					<property name="publishedRenderers">form, capabilities</property>
					<dbCore queriedTable="something"/>
				</service>
			"""))
		rd.sourceId = "test/fake"
		return getGetRecordResponse(rd)


class ExoticRDPubVORTest(testhelpers.VerboseTest):
	resources = [("textAndTree", _ExoticRDVOR())]

	def testPublishedRenderersHonored(self):
		caps = [c.get("standardID")
			for c in self.textAndTree[1].xpath("//capability")]
		self.assertEqual(caps, [None, 'ivo://ivoa.net/std/VOSI#capabilities'])

	def testNoTAPcap(self):
		# no ADQL table, no aux cap
		self.assertEqual(len(self.textAndTree[1].xpath(
				"//capability[@standardID='ivo://ivoa.net/std/TAP#aux']")), 0)

	def testNoTableset(self):
		self.assertEqual(len(self.textAndTree[1].xpath("//tableset")), 0)


# minimal meta for successful RR generation without a (working) RD
_fakeMeta ="""<meta name="identifier">ivo://gavo.testing</meta>
<meta name="datetimeUpdated">2000-00-00T00:00:00</meta>
<meta name="referenceURL">http://faked</meta>
<meta name="recTimestamp">2000-00-00T00:00:00</meta>
<meta name="sets">ivo_managed</meta>"""


class RelatedTest(testhelpers.VerboseTest):
# Tests for everything to do with the "related" meta
	def _getTreeFor(self, dataBody):
		rd = base.parseFromString(rscdesc.RD,
			"""<resource schema="test"><table id="foo">%s%s</table></resource>"""%(
				dataBody, _fakeMeta))
		td = rd.tables[0]
		return testhelpers.getXMLTree(
			builders.getVOResourceElement(td).render(), debug=False)

	def testNoRelations(self):
		tree = self._getTreeFor("")
		self.assertFalse(tree.xpath("metadata/Resource/content/relationship"))

	def testSimpleRelation(self):
		tree = self._getTreeFor(
			'<meta name="servedBy" ivoId="ivo://glub">The Glub Data</meta>')
		relEl = tree.xpath("metadata/Resource/content/relationship")
		self.assertTrue(relEl)
		self.assertEqual(relEl[0].xpath("relationshipType")[0].text,
			"IsServedBy")
		self.assertEqual(relEl[0].xpath("relatedResource")[0].text,
			"The Glub Data")
		self.assertEqual(relEl[0].xpath("relatedResource")[0].attrib["ivo-id"],
			"ivo://glub")

	def testRelationAltIdentifer(self):
		tree = self._getTreeFor(
			'<meta name="isSupplementTo" altIdentifier="doi:10.2030/glub">'
			'The Glub Data</meta>')
		relEl = tree.xpath("metadata/Resource/content/relationship")
		self.assertTrue(relEl)
		self.assertEqual(relEl[0].xpath("relationshipType")[0].text,
			"IsSupplementTo")
		self.assertEqual(relEl[0].xpath("relatedResource")[0].text,
			"The Glub Data")
		self.assertEqual(relEl[0].xpath("relatedResource"
			)[0].attrib["altIdentifier"], "doi:10.2030/glub")

	def testReset(self):
		self._getTreeFor(
			'<meta name="servedBy" ivoId="ivo://glub">The Glub Data</meta>')
		tree1 = self._getTreeFor(
			'<meta name="servedBy" ivoId="ivo://frob">The Frob Data</meta>')
		# we once had a bug where the builder didn't get reset, and due to
		# bad arch I think it'll come again.  Therefore this test -- it
		# will catch this.
		self.assertEqual(len(tree1.xpath(
			"metadata/Resource/content/relationship/relatedResource")), 1)

	def testRegistration(self):
		try:
			tree = self._getTreeFor(
				'<register services="//adql#query"/>')
			relEl = tree.xpath("metadata/Resource/content/relationship")
			self.assertTrue(relEl)
			self.assertEqual(relEl[0].xpath("relationshipType")[0].text,
				"IsServedBy")
			self.assertEqual(relEl[0].xpath("relatedResource")[0].attrib["ivo-id"],
				"ivo://%s/__system__/adql/query"%base.getConfig("ivoa", "authority"))

			# also check metadata on the exposing end
			svc = base.caches.getRD("//adql").getById("query")
			svcTree = testhelpers.getXMLTree(
				builders.getVOResourceElement(svc).render(), debug=False)
			for rel in svcTree.xpath("metadata/Resource/content/relationship"):
				if rel.xpath("relationshipType")[0].text=="IsServiceFor":
					for dest in rel.xpath("relatedResource"):
						if dest.attrib.get("ivo-id")=="ivo://gavo.testing":
							return
			# Fallthrough: The reference to our test service has not been found
			self.fail("Data registration did not leave isServiceFor meta")
		finally:
			# clear adql entry in cache since we've changed it
			base.caches.clearForName("//adql")

	def testSupplement(self):
		tree = self._getTreeFor(
			'<meta name="isSupplementTo" ivoId="ivo://test/table">Test Table</meta>')
		self.assertEqual(tree.xpath(
			"metadata/Resource/content/relationship/relatedResource")[0
				].get("ivo-id"), "ivo://test/table")
		self.assertEqual(tree.xpath(
			"metadata/Resource/content/relationship/relatedResource")[0
				].text, "Test Table")
		self.assertEqual(tree.xpath(
			"metadata/Resource/content/relationship/relationshipType")[0
			].text, "IsSupplementTo")


class IdResolutionTest(testhelpers.VerboseTest):
	auth = base.getConfig("ivoa", "authority")

	def testNormal(self):
		# (id is rdid/id)
		svc = registry.getResobFromIdentifier(
			"ivo://%s/tap"%self.auth)
		self.assertEqual(
			base.getMetaText(svc, "title"),
			"Unittest Suite TAP service")

	def testAuthority(self):
		rec = registry.getResobFromIdentifier("ivo://%s"%self.auth)
		self.assertTrue(isinstance(rec, registry.nonservice.ResRec))
		self.assertEqual(registry.getResType(rec), "authority")
		self.assertTrue(base.getMetaText(rec, "description").startswith(
			" UNCONFIGURED"))

	def testOrganization(self):
		with testhelpers.userconfigContent(r"""
				<STREAM id="registry-interfacerecords">
				<resRec id="org">
					<meta>				
						resType: organization
						creationDate: \\metaString{authority.creationDate}{UNCONFIGURED}
						title: \\metaString{authority.title}{UNCONFIGURED}
						shortName: \\metaString{authority.shortName}{UNCONFIGURED} org
						subject: Organisation
						referenceURL: http://your.institution/home
						identifier: ivo://\getConfig{ivoa}{authority}/org
						sets: ivo_managed
					</meta>
					<meta name="description"
						>A test organisation
					</meta>
				</resRec>
				</STREAM>"""):
			base.caches.clearForName("__system__/services")
			publication.updateServiceList([base.resolveCrossId("//services")])
			rec = registry.getResobFromIdentifier("ivo://%s/org"%self.auth)
			self.assertTrue(isinstance(rec, registry.nonservice.ResRec))
			self.assertEqual(registry.getResType(rec), "organization")
			self.assertEqual(base.getMetaText(rec, "referenceURL"),
				"http://your.institution/home")
			self.assertEqual(base.getMetaText(rec, "description"),
				"A test organisation")
			self.assertEqual(base.getMetaText(rec, "sets"),
				"ivo_managed")

		base.caches.clearForName("__system__/services")
		publication.updateServiceList([base.resolveCrossId("//services")])

	def testBadId(self):
		self.assertRaises(registry.IdDoesNotExist,
			registry.getResobFromIdentifier,
			"ivo://junk/blastes")


class _ListIdentifiersResponse(testhelpers.TestResource):
	def make(self, ignored):
		return testhelpers.getXMLTree(
			oaiinter.runPMH({"verb": "ListIdentifiers", "metadataPrefix": "ivo_vor"},
				oaiinter.RegistryCore.builders).render(), debug=False)


class ListIdentifiersTest(testhelpers.VerboseTest):
	resources = [("tree", _ListIdentifiersResponse())]

	def testRegistryAndAuthorityPresent(self):
		res = set(el.text for el in self.tree.xpath("//identifier"))
		expected = set([
			"ivo://x-testing/__system__/services/registry",
			"ivo://x-testing"])
		self.assertEqual(res&expected, expected)

	def testSetSpec(self):
		sets = self.tree.xpath(
			"//header[identifier='ivo://x-testing']/setSpec")
		self.assertEqual(testhelpers.pickSingle(sets).text, "ivo_managed")


class OAIParameterTest(testhelpers.VerboseTest):
	def testEqualGranularityEnforced(self):
		self.assertRaisesWithMsg(oaiinter.BadArgument,
			"from",
			oaiinter.runPMH,
			({"verb": "ListIdentifiers", "metadataPrefix": "ivo_vor",
				"from": "2010-10-10", "until": "2011-10-10T10:10:10Z"},
				oaiinter.RegistryCore.builders))


class ResumptionTokenTest(testhelpers.VerboseTest):
	def testBasic(self):
		pars = {"verb": "listSets"}
		pars["resumptionToken"] = oaiinter.makeResumptionToken(pars, 20)
		newPars = oaiinter.parseResumptionToken(pars)
		self.assertEqual(pars["verb"], newPars["verb"])
		self.assertEqual(newPars["resumptionToken"], 20)

	def testBadTokenFailsProperly(self):
		self.assertRaisesWithMsg(oaiinter.BadResumptionToken,
			"Incorrect padding",
			oaiinter.parseResumptionToken,
			({"resumptionToken": "xyz"},))


class MetaExpandedTest(testhelpers.VerboseTest):
	def testWithTAPRecord(self):
		rd = base.caches.getRD("//tap")
		rec = publication.iterSvcRecs(rd.services[0]).__next__()[-1]
		self.assertTrue(rec["description"].startswith(
			"The Unittest Suite's TAP end point. The Table Access"))
		self.assertEqual(rec["shortName"], "<13 chrs TAP")


class ResourceLocationTest(testhelpers.VerboseTest):
	def testCaseyTableNamesOk(self):
		self.assertEqual(registry.getTableDef("ivoa.obsCore").getQName(),
			"ivoa.ObsCore")


class _OtherServiceRRTree(testhelpers.TestResource):
	def make(self, deps):
		rd = base.parseFromString(rscdesc.RD, """
			<resource schema="test">
				<meta name="datetimeUpdated">2000-01-01T00:00:00</meta>
				<meta name="creationDate">2000-01-01T00:00:00</meta>
				<meta name="referenceURL">http://faked</meta>
				<meta name="description">All Faked</meta>
				<meta name="subject">Fakes</meta>

				<meta name="productTypeServed">image</meta>
				<meta name="productTypeServed">polarized-spectrum</meta>

				<dbCore id="adcore" queriedTable="data/test#adqltable"/>
				<service id="web" core="adcore">
					<meta name="title">Web service</meta>
					<publish render="form" sets="local"/>
				</service>
				<service id="scs" allowed="scs.xml" core="adcore">
					<meta name="title">SCS service</meta>
					<meta name="shortName">test prot</meta>
					<meta name="testQuery.ra">1</meta>
					<meta name="testQuery.dec">1</meta>
					<meta name="testQuery.sr">1</meta>
					<publish render="scs.xml" sets="ivo_managed">
						<meta name="mirrorURL">http://somewhere.el.se/fakemirror?</meta>
						<meta name="mirrorURL">http://right.the.re/fakemirror?</meta>
						<meta name="mirrorURL">http://localhost:8080/k/scs/scs.xml?</meta>
					</publish>
					<publish render="form" sets="ivo_managed" service="web"/>
					</service></resource>
				""")
		rd.sourceId = "k"

		from gavo.registry import publication
		intfRecs, resRecs = [], []
		for key, rec in publication.iterSvcRecs(rd.services[1]):
			if key=="interfaces":
				intfRecs.append(rec)
			elif key=="resources":
				resRecs.append(rec)

		return (testhelpers.getXMLTree(
				builders.getVOResourceElement(rd.services[1]).render(), debug=False),
			intfRecs, resRecs)


class OtherServiceTest(testhelpers.VerboseTest):
# tests for having a service attribute in a publish record,
# and for automatic addition of TAP aux records.
	resources = [("treeAndRecs", _OtherServiceRRTree())]

	def testSCSCapability(self):
		self.assertEqual(self.treeAndRecs[0].xpath(
				"//capability[1]/interface/accessURL")[0].text,
			"http://localhost:8080/k/scs/scs.xml?")
		self.assertEqual(self.treeAndRecs[0].xpath(
				"//capability[@standardID='ivo://ivoa.net/std/conesearch']"
				"/maxSR")[0].text, "180")
		self.assertEqual(self.treeAndRecs[0].xpath(
				"//capability[@standardID='ivo://ivoa.net/std/conesearch']"
				"/testQuery/ra")[0].text, "1")

	def testWebCapability(self):
		self.assertEqual(self.treeAndRecs[0].xpath(
				"//capability[2]/interface/accessURL")[0].text,
			"http://localhost:8080/k/web/form")
	
	def testTAPAuxCap(self):
		self.assertEqual(self.treeAndRecs[0].xpath(
				"//capability[@standardID='ivo://ivoa.net/std/TAP#aux']"
					"/interface/accessURL")[0].text,
			"http://localhost:8080/tap")

	def testDBFormLink(self):
		webIntf = [r for r in self.treeAndRecs[1] if r["browseable"]]
		assert len(webIntf)==1
		self.assertEqual(webIntf[0]["referenceURL"],
			"http://localhost:8080/k/web/info")
		self.assertEqual(webIntf[0]["accessURL"],
			"/k/web/form")

	def testDBCSLink(self):
		csIntf = [r for r in self.treeAndRecs[1] if r["renderer"]=="scs.xml"]
		assert len(csIntf)==1
		self.assertFalse(csIntf[0]["browseable"])
		self.assertEqual(csIntf[0]["referenceURL"],
			"http://localhost:8080/k/scs/info")
		self.assertEqual(csIntf[0]["accessURL"],
			"/k/scs/scs.xml?")

	def testMirrors(self):
		intf = self.treeAndRecs[0].xpath(
				"//capability[@standardID='ivo://ivoa.net/std/conesearch']/"
				"interface[@role='std']")[0]
		self.assertEqual(intf.xpath("mirrorURL")[0].text,
				"http://somewhere.el.se/fakemirror?")
		self.assertEqual(intf.xpath("mirrorURL")[1].text,
				"http://right.the.re/fakemirror?")

	def testSetSpec(self):
		self.assertEqual([n.text for n in
			self.treeAndRecs[0].xpath("header/setSpec")],
			["ivo_managed"])
	
	def testTitle(self):
		self.assertEqual(self.treeAndRecs[2][0]["title"], "SCS service")

	def testProductType(self):
		self.assertEqual(
			[el.text for el in self.treeAndRecs[0].xpath("//productTypeServed")],
			['image', 'polarized-spectrum'])


class TAPDateBumpingTest(testhelpers.VerboseTest):
	def setUp(self):
		with base.getWritableAdminConn() as conn:
			conn.execute("UPDATE dc.resources"
				" SET rectimestamp='2000-01-01'"
				" WHERE sourcerd='__system__/tap'")

	def testTAPTimestampUpdatedWithADQL(self):
		publication.updateServiceList([base.caches.getRD("data/ssatest")])
		updated = [getattr(o, "getFullId", lambda: "junk")()
			for o in oaiinter.getMatchingResobs(
				{"from": datetime.date.today().isoformat()})]
		self.assertTrue('data/ssatest#c' in updated)
		self.assertTrue('__system__/tap#run' in updated)
		
	def testTAPTimestampConstantWithoutADQL(self):
		# data/cores has no adql-published tables in it: no TAP record
		publication.updateServiceList([base.caches.getRD("data/cores")])
		updated = [getattr(o, "getFullId", lambda: "no-full-id-sorry")()
			for o in oaiinter.getMatchingResobs(
				{"from": datetime.date.today().isoformat()})]
		self.assertTrue('data/cores#pc' in updated)
		self.assertFalse('__system__/tap#run' in updated)


class TablesetComputationTest(testhelpers.VerboseTest):
	@staticmethod
	def _getTablesetTreeFor(rdCls, rdSrc, resId=None):
		res = api.parseFromString(rdCls, rdSrc)
		if resId:
			res = res.getById(resId)
		ts = tableset.getTablesetForService(res)
		return testhelpers.getXMLTree(ts.render(), debug=False)

	def testTablesetWithOutputTable(self):
		tree = self._getTablesetTreeFor(svcs.Service,
			"""<service id="a"><dbCore queriedTable="//tap#tables"/>
				<outputTable><column name="x"/></outputTable>
				</service>""")
		self.assertEqual(len(tree.xpath("//table")), 1)
		self.assertEqual(len(tree.xpath("//table[@type='output']")), 1)

	def testTablesetWithDbCore(self):
		tree = self._getTablesetTreeFor(svcs.Service,
			"""<service id="a"><dbCore queriedTable="//tap#tables"/>
				</service>""")
		self.assertEqual(len(tree.xpath("//table")), 1)
		self.assertEqual(tree.xpath("//table/name")[0].text, "tap_schema.tables")

	def testTablesetWithTAPCore(self):
		tree = self._getTablesetTreeFor(svcs.Service,
			"""<service id="a"><tapCore/></service>""")
		self.assertTrue(len(tree.xpath("//table"))>5) # at least TAP_SCHEMA
		self.assertEqual(
			len(tree.xpath("//table[name='tap_schema.key_columns']/column")),
			4)
		self.assertEqual(tree.xpath("//table[name='tap_schema.tables']"
			"/foreignKey[targetTable='tap_schema.schemas']"
			"/fkColumn/fromColumn")[0].text,
			"schema_name")

	def testTablesetForData(self):
		tree = self._getTablesetTreeFor(api.RD, """<resource schema="test">
			<table id="helper" onDisk="True" adql="hidden">
				<column name="hidethis"/></table>
			<table id="data" onDisk="True" adql="True">
				<column name="visthis"/></table>
			<data id="pub">
				<publish/>
				<make table="helper"/>
				<make table="data"/>
			</data></resource>""", "pub")
		self.assertEqual(len(tree.xpath("//table")), 1)
		self.assertEqual(tree.xpath("//table/name")[0].text, "test.data")

	def testTablePublishedEvenNonTAP(self):
		# It's not altogether clear if it's useful to publish a table
		# schema when it's not tap-queriable, and perhaps we ought to
		# qualify that; but then again, if folks explicitly order
		# table publication, I guess I should honour that.
		tree = self._getTablesetTreeFor(api.RD, """<resource schema="test">
			<table id="pub" onDisk="True">
				<publish/>
				<column name="notinvisible"/></table>
			</resource>""", "pub")
		self.assertEqual(len(tree.xpath("//tableset")), 1)
		self.assertEqual(len(tree.xpath("//column[name='notinvisible']")), 1)


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