@@ -1981,3 +1981,175 @@ test("parse a Hydra documentation with owl:equivalentClass without onProperty hy
19811981 expect ( bookConditionResource . name ) . toBe ( "book_conditions" ) ;
19821982 expect ( bookConditionResource . title ) . toBe ( "BookCondition" ) ;
19831983} ) ;
1984+
1985+ test ( "parse a Hydra documentation with read-only resource without hydra prefix (bare member)" , async ( ) => {
1986+ const readOnlyEntrypoint = {
1987+ "@context" : {
1988+ "@vocab" : "http://localhost/docs.jsonld#" ,
1989+ hydra : "http://www.w3.org/ns/hydra/core#" ,
1990+ greeting : {
1991+ "@id" : "Entrypoint/greeting" ,
1992+ "@type" : "@id" ,
1993+ } ,
1994+ } ,
1995+ "@id" : "/" ,
1996+ "@type" : "Entrypoint" ,
1997+ greeting : "/greetings" ,
1998+ } ;
1999+
2000+ // Simulate hydra_prefix: false — "member" is NOT prefixed with "hydra:"
2001+ // so jsonld.expand will resolve it via @vocab instead of the hydra context
2002+ const readOnlyDocs = {
2003+ "@context" : {
2004+ "@vocab" : "http://localhost/docs.jsonld#" ,
2005+ hydra : "http://www.w3.org/ns/hydra/core#" ,
2006+ rdf : "http://www.w3.org/1999/02/22-rdf-syntax-ns#" ,
2007+ rdfs : "http://www.w3.org/2000/01/rdf-schema#" ,
2008+ xmls : "http://www.w3.org/2001/XMLSchema#" ,
2009+ owl : "http://www.w3.org/2002/07/owl#" ,
2010+ domain : {
2011+ "@id" : "rdfs:domain" ,
2012+ "@type" : "@id" ,
2013+ } ,
2014+ range : {
2015+ "@id" : "rdfs:range" ,
2016+ "@type" : "@id" ,
2017+ } ,
2018+ subClassOf : {
2019+ "@id" : "rdfs:subClassOf" ,
2020+ "@type" : "@id" ,
2021+ } ,
2022+ expects : {
2023+ "@id" : "hydra:expects" ,
2024+ "@type" : "@id" ,
2025+ } ,
2026+ returns : {
2027+ "@id" : "hydra:returns" ,
2028+ "@type" : "@id" ,
2029+ } ,
2030+ } ,
2031+ "@id" : "/docs.jsonld" ,
2032+ "hydra:title" : "API with read-only resource" ,
2033+ "hydra:description" : "An API with a read-only resource and no hydra prefix" ,
2034+ "hydra:entrypoint" : "/" ,
2035+ "hydra:supportedClass" : [
2036+ {
2037+ "@id" : "#Greeting" ,
2038+ "@type" : "hydra:Class" ,
2039+ "rdfs:label" : "Greeting" ,
2040+ "hydra:title" : "Greeting" ,
2041+ "hydra:supportedProperty" : [
2042+ {
2043+ "@type" : "hydra:SupportedProperty" ,
2044+ "hydra:property" : {
2045+ "@id" : "#Greeting/message" ,
2046+ "@type" : "rdf:Property" ,
2047+ "rdfs:label" : "message" ,
2048+ domain : "#Greeting" ,
2049+ range : "xmls:string" ,
2050+ } ,
2051+ "hydra:title" : "message" ,
2052+ "hydra:required" : false ,
2053+ "hydra:readable" : true ,
2054+ "hydra:writeable" : false ,
2055+ } ,
2056+ ] ,
2057+ "hydra:supportedOperation" : [
2058+ {
2059+ "@type" : "hydra:Operation" ,
2060+ "hydra:method" : "GET" ,
2061+ "hydra:title" : "Retrieves Greeting resource." ,
2062+ "rdfs:label" : "Retrieves Greeting resource." ,
2063+ returns : "#Greeting" ,
2064+ } ,
2065+ ] ,
2066+ } ,
2067+ {
2068+ "@id" : "#Entrypoint" ,
2069+ "@type" : "hydra:Class" ,
2070+ "hydra:title" : "The API entrypoint" ,
2071+ "hydra:supportedProperty" : [
2072+ {
2073+ "@type" : "hydra:SupportedProperty" ,
2074+ "hydra:property" : {
2075+ "@id" : "#Entrypoint/greeting" ,
2076+ "@type" : "hydra:Link" ,
2077+ domain : "#Entrypoint" ,
2078+ "rdfs:label" : "The collection of Greeting resources" ,
2079+ "rdfs:range" : [
2080+ { "@id" : "hydra:PagedCollection" } ,
2081+ {
2082+ "owl:equivalentClass" : {
2083+ // bare "member" without hydra: prefix — this is the bug trigger
2084+ "owl:onProperty" : { "@id" : "member" } ,
2085+ "owl:allValuesFrom" : { "@id" : "#Greeting" } ,
2086+ } ,
2087+ } ,
2088+ ] ,
2089+ "hydra:supportedOperation" : [
2090+ {
2091+ "@type" : "hydra:Operation" ,
2092+ "hydra:method" : "GET" ,
2093+ "hydra:title" :
2094+ "Retrieves the collection of Greeting resources." ,
2095+ "rdfs:label" :
2096+ "Retrieves the collection of Greeting resources." ,
2097+ returns : "hydra:PagedCollection" ,
2098+ } ,
2099+ ] ,
2100+ } ,
2101+ "hydra:title" : "The collection of Greeting resources" ,
2102+ "hydra:readable" : true ,
2103+ "hydra:writeable" : false ,
2104+ } ,
2105+ ] ,
2106+ "hydra:supportedOperation" : {
2107+ "@type" : "hydra:Operation" ,
2108+ "hydra:method" : "GET" ,
2109+ "rdfs:label" : "The API entrypoint." ,
2110+ returns : "#EntryPoint" ,
2111+ } ,
2112+ } ,
2113+ {
2114+ "@id" : "#ConstraintViolation" ,
2115+ "@type" : "hydra:Class" ,
2116+ "hydra:title" : "A constraint violation" ,
2117+ "hydra:supportedProperty" : [ ] ,
2118+ } ,
2119+ {
2120+ "@id" : "#ConstraintViolationList" ,
2121+ "@type" : "hydra:Class" ,
2122+ subClassOf : "hydra:Error" ,
2123+ "hydra:title" : "A constraint violation list" ,
2124+ "hydra:supportedProperty" : [ ] ,
2125+ } ,
2126+ ] ,
2127+ } ;
2128+
2129+ server . use (
2130+ http . get ( "http://localhost" , ( ) => Response . json ( readOnlyEntrypoint , init ) ) ,
2131+ http . get ( "http://localhost/docs.jsonld" , ( ) =>
2132+ Response . json ( readOnlyDocs , init ) ,
2133+ ) ,
2134+ ) ;
2135+
2136+ const data = await parseHydraDocumentation ( "http://localhost" ) ;
2137+ expect ( data . status ) . toBe ( 200 ) ;
2138+
2139+ const greetingResource = data . api . resources ?. find (
2140+ ( r ) => r . id === "http://localhost/docs.jsonld#Greeting" ,
2141+ ) ;
2142+
2143+ expect ( greetingResource ) . toBeDefined ( ) ;
2144+ assert ( greetingResource !== undefined ) ;
2145+ expect ( greetingResource . name ) . toBe ( "greetings" ) ;
2146+ expect ( greetingResource . title ) . toBe ( "Greeting" ) ;
2147+
2148+ // Verify read-only: readable but not writable
2149+ assert ( greetingResource . fields !== null ) ;
2150+ assert ( greetingResource . fields !== undefined ) ;
2151+ expect ( greetingResource . fields ) . toHaveLength ( 1 ) ;
2152+ expect ( greetingResource . fields [ 0 ] ?. name ) . toBe ( "message" ) ;
2153+ expect ( greetingResource . readableFields ) . toHaveLength ( 1 ) ;
2154+ expect ( greetingResource . writableFields ) . toHaveLength ( 0 ) ;
2155+ } ) ;
0 commit comments