ROOTPLOIT
Server: LiteSpeed
System: Linux in-mum-web1878.main-hosting.eu 5.14.0-570.21.1.el9_6.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Jun 11 07:22:35 EDT 2025 x86_64
User: u435929562 (435929562)
PHP: 7.4.33
Disabled: system, exec, shell_exec, passthru, mysql_list_dbs, ini_alter, dl, symlink, link, chgrp, leak, popen, apache_child_terminate, virtual, mb_send_mail
Upload Files
File: //proc/thread-self/root/opt/go/pkg/mod/github.com/go-openapi/[email protected]/expander_test.go
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package spec

import (
	"encoding/json"
	"io"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

const (
	crossFileRefFixture = "fixtures/expansion/crossFileRef.json"
	withoutSchemaID     = "removed"
	withSchemaID        = "schema"
	pathItemsFixture    = "fixtures/expansion/pathItems.json"
	extraRefFixture     = "fixtures/expansion/extraRef.json"
)

var (
	// PetStoreJSONMessage json raw message for Petstore20
	PetStoreJSONMessage = json.RawMessage([]byte(PetStore20))
	specs               = filepath.Join("fixtures", "specs")
)

func TestExpand_Issue148(t *testing.T) {
	fp := filepath.Join("fixtures", "bugs", "schema-148.json")
	b, err := jsonDoc(fp)
	require.NoError(t, err)

	assertAdditionalProps := func(sp Swagger) func(*testing.T) {
		return func(t *testing.T) {
			require.Len(t, sp.Definitions, 2)

			require.Contains(t, sp.Definitions, "empty")
			empty := sp.Definitions["empty"]
			require.NotNil(t, empty.AdditionalProperties)
			require.NotNil(t, empty.AdditionalProperties.Schema)
			require.True(t, empty.AdditionalProperties.Allows)

			require.Contains(t, sp.Definitions, "false")
			additionalIsFalse := sp.Definitions["false"]
			require.NotNil(t, additionalIsFalse.AdditionalProperties)
			require.Nil(t, additionalIsFalse.AdditionalProperties.Schema)
			require.False(t, additionalIsFalse.AdditionalProperties.Allows)
		}
	}

	var sp Swagger
	require.NoError(t, json.Unmarshal(b, &sp))
	t.Run("check additional properties", assertAdditionalProps(sp))

	require.NoError(t, ExpandSpec(&sp, nil))
	t.Run("check additional properties after expansion", assertAdditionalProps(sp))
}

func TestExpand_KnownRef(t *testing.T) {
	// json schema draft 4 meta schema is embedded by default: it resolves without setup
	schema := RefProperty("http://json-schema.org/draft-04/schema#")
	require.NoError(t, ExpandSchema(schema, nil, nil))

	assert.Equal(t, "Core schema meta-schema", schema.Description)

	// from the expanded schema, verify that all remaining $ref actually resolve
	jazon := asJSON(t, schema)

	assertRefResolve(t, jazon, "", schema)
}

func TestExpand_ResponseSchema(t *testing.T) {
	fp := filepath.Join("fixtures", "local_expansion", "spec.json")
	b, err := jsonDoc(fp)
	require.NoError(t, err)

	var spec Swagger
	require.NoError(t, json.Unmarshal(b, &spec))

	require.NoError(t, ExpandSpec(&spec, &ExpandOptions{RelativeBase: fp}))

	// verify that the document is full expanded
	jazon := asJSON(t, spec)
	assertNoRef(t, jazon)

	sch := spec.Paths.Paths["/item"].Get.Responses.StatusCodeResponses[200].Schema
	require.NotNil(t, sch)

	assert.Empty(t, sch.Ref.String())
	assert.Contains(t, sch.Type, "object")
	assert.Len(t, sch.Properties, 2)
}

func TestExpand_EmptySpec(t *testing.T) {
	spec := new(Swagger)

	// expansion of an empty spec
	require.NoError(t, ExpandSpec(spec, nil))

	// expansion of a nil schema
	var schema *Schema
	require.NoError(t, ExpandSchemaWithBasePath(schema, nil, nil))

	// expansion of a nil paths
	var paths *PathItem
	resolver := defaultSchemaLoader(spec, nil, nil, nil)
	require.NoError(t, expandPathItem(paths, resolver, ""))

	// expansion of a nil Parameter
	var param *Parameter
	require.NoError(t, expandParameterOrResponse(param, resolver, ""))
}

func TestExpand_Spec(t *testing.T) {

	// expansion of a rich spec
	specPath := filepath.Join("fixtures", "expansion", "all-the-things.json")
	specDoc, err := jsonDoc(specPath)
	require.NoError(t, err)

	opts := &ExpandOptions{
		RelativeBase: specPath,
	}

	spec := new(Swagger)
	require.NoError(t, json.Unmarshal(specDoc, spec))

	// verify the resulting unmarshaled structure
	pet := spec.Definitions["pet"]
	errorModel := spec.Definitions["errorModel"]
	petResponse := spec.Responses["petResponse"]
	petResponse.Schema = &pet
	stringResponse := spec.Responses["stringResponse"]
	tagParam := spec.Parameters["tag"]
	idParam := spec.Parameters["idParam"]

	require.NoError(t, ExpandSpec(spec, opts))

	// verify that the spec is fully expanded
	assertNoRef(t, asJSON(t, spec))

	assert.Equal(t, tagParam, spec.Parameters["query"])
	assert.Equal(t, petResponse, spec.Responses["petResponse"])
	assert.Equal(t, petResponse, spec.Responses["anotherPet"])
	assert.Equal(t, pet, *spec.Responses["petResponse"].Schema)
	assert.Equal(t, stringResponse, *spec.Paths.Paths["/"].Get.Responses.Default)
	assert.Equal(t, petResponse, spec.Paths.Paths["/"].Get.Responses.StatusCodeResponses[200])
	assert.Equal(t, pet, *spec.Paths.Paths["/pets"].Get.Responses.StatusCodeResponses[200].Schema.Items.Schema)
	assert.Equal(t, errorModel, *spec.Paths.Paths["/pets"].Get.Responses.Default.Schema)
	assert.Equal(t, pet, spec.Definitions["petInput"].AllOf[0])
	assert.Equal(t, spec.Definitions["petInput"], *spec.Paths.Paths["/pets"].Post.Parameters[0].Schema)
	assert.Equal(t, petResponse, spec.Paths.Paths["/pets"].Post.Responses.StatusCodeResponses[200])
	assert.Equal(t, errorModel, *spec.Paths.Paths["/pets"].Post.Responses.Default.Schema)

	pi := spec.Paths.Paths["/pets/{id}"]
	assert.Equal(t, idParam, pi.Get.Parameters[0])
	assert.Equal(t, petResponse, pi.Get.Responses.StatusCodeResponses[200])
	assert.Equal(t, errorModel, *pi.Get.Responses.Default.Schema)
	assert.Equal(t, idParam, pi.Delete.Parameters[0])
	assert.Equal(t, errorModel, *pi.Delete.Responses.Default.Schema)
}

func TestExpand_InternalResponse(t *testing.T) {
	basePath := normalizeBase(filepath.Join("fixtures", "expansion", "all-the-things.json"))
	specDoc, err := jsonDoc(basePath)
	require.NoError(t, err)

	spec := new(Swagger)
	require.NoError(t, json.Unmarshal(specDoc, spec))

	resolver := defaultSchemaLoader(spec, nil, nil, nil)

	expectedPet := spec.Responses["petResponse"]
	require.NoError(t, expandParameterOrResponse(&expectedPet, resolver, basePath))

	jazon := asJSON(t, expectedPet)

	assert.JSONEq(t, `{
         "description": "pet response",
         "schema": {
          "required": [
           "id",
           "name"
          ],
          "properties": {
           "id": {
            "type": "integer",
            "format": "int64"
           },
           "name": {
            "type": "string"
           },
           "tag": {
            "type": "string"
           }
          }
         }
			 }`, jazon)

	// response pointing to the same target: result is unchanged
	another := spec.Responses["anotherPet"]
	require.NoError(t, expandParameterOrResponse(&another, resolver, basePath))
	assert.Equal(t, expectedPet, another)

	defaultResponse := spec.Paths.Paths["/"].Get.Responses.Default

	require.NoError(t, expandParameterOrResponse(defaultResponse, resolver, basePath))

	expectedString := spec.Responses["stringResponse"]
	assert.Equal(t, expectedString, *defaultResponse)

	// cascading ref
	successResponse := spec.Paths.Paths["/"].Get.Responses.StatusCodeResponses[200]

	jazon = asJSON(t, successResponse)

	assert.JSONEq(t, `{
		"$ref": "#/responses/anotherPet"
  }`, jazon)

	require.NoError(t, expandParameterOrResponse(&successResponse, resolver, basePath))
	assert.Equal(t, expectedPet, successResponse)
}

func TestExpand_Response(t *testing.T) {
	basePath := filepath.Join("fixtures", "expansion", "all-the-things.json")

	specDoc, err := jsonDoc(basePath)
	require.NoError(t, err)

	spec := new(Swagger)
	require.NoError(t, json.Unmarshal(specDoc, spec))

	resp := spec.Responses["anotherPet"]
	expected := spec.Responses["petResponse"]

	require.NoError(t, ExpandResponse(&expected, basePath))

	require.NoError(t, ExpandResponse(&resp, basePath))
	assert.Equal(t, expected, resp)

	resp2 := spec.Paths.Paths["/"].Get.Responses.Default
	expected = spec.Responses["stringResponse"]

	require.NoError(t, ExpandResponse(resp2, basePath))
	assert.Equal(t, expected, *resp2)

	resp = spec.Paths.Paths["/"].Get.Responses.StatusCodeResponses[200]
	expected = spec.Responses["petResponse"]

	require.NoError(t, ExpandResponse(&resp, basePath))
	assert.Equal(t, expected, resp)
}

func TestExpand_ResponseAndParamWithRoot(t *testing.T) {
	specDoc, err := jsonDoc("fixtures/bugs/1614/gitea.json")
	require.NoError(t, err)

	var spec Swagger
	err = json.Unmarshal(specDoc, &spec)
	require.NoError(t, err)

	// check responses with $ref
	resp := spec.Paths.Paths["/admin/users"].Post.Responses.StatusCodeResponses[201]
	require.NoError(t, ExpandResponseWithRoot(&resp, spec, nil))

	jazon := asJSON(t, resp)
	assertNoRef(t, jazon)

	resp = spec.Paths.Paths["/admin/users"].Post.Responses.StatusCodeResponses[403]
	require.NoError(t, ExpandResponseWithRoot(&resp, spec, nil))

	jazon = asJSON(t, resp)
	assertNoRef(t, jazon)

	// check param with $ref
	param := spec.Paths.Paths["/admin/users"].Post.Parameters[0]
	require.NoError(t, ExpandParameterWithRoot(&param, spec, nil))

	jazon = asJSON(t, param)
	assertNoRef(t, jazon)
}

func TestExpand_InternalParameter(t *testing.T) {
	basePath := normalizeBase(filepath.Join("fixtures", "expansion", "params.json"))

	paramDoc, err := jsonDoc(basePath)
	require.NoError(t, err)

	spec := new(Swagger)
	require.NoError(t, json.Unmarshal(paramDoc, spec))

	resolver := defaultSchemaLoader(spec, nil, nil, nil)

	param := spec.Parameters["query"]
	expected := spec.Parameters["tag"]

	require.NoError(t, expandParameterOrResponse(&param, resolver, basePath))

	assert.Equal(t, expected, param)

	param = spec.Paths.Paths["/cars/{id}"].Parameters[0]
	expected = spec.Parameters["id"]

	require.NoError(t, expandParameterOrResponse(&param, resolver, basePath))

	assert.Equal(t, expected, param)
}

func TestExpand_Parameter(t *testing.T) {
	basePath := filepath.Join("fixtures", "expansion", "params.json")

	paramDoc, err := jsonDoc(basePath)
	require.NoError(t, err)

	spec := new(Swagger)
	require.NoError(t, json.Unmarshal(paramDoc, spec))

	param := spec.Parameters["query"]
	expected := spec.Parameters["tag"]

	require.NoError(t, ExpandParameter(&param, basePath))
	assert.Equal(t, expected, param)

	param = spec.Paths.Paths["/cars/{id}"].Parameters[0]
	expected = spec.Parameters["id"]

	require.NoError(t, ExpandParameter(&param, basePath))
	assert.Equal(t, expected, param)
}

func TestExpand_JSONSchemaDraft4(t *testing.T) {
	fixturePath := filepath.Join("schemas", "jsonschema-draft-04.json")
	jazon, sch := expandThisSchemaOrDieTrying(t, fixturePath)

	// expansion leaves some circular $ref behind: let's assert those

	// assert all $ref match
	// "$ref": "http://json-schema.org/draft-04/something"
	//
	// Case: Circular $refs against schema ID
	assertRefInJSONRegexp(t, jazon, `^(http://json-schema.org/draft-04/)|(#/definitions/schemaArray)`)

	// verify that all remaining $ref may be expanded in the new root schema
	assertRefExpand(t, jazon, "", sch)
}

func TestExpand_SwaggerSchema(t *testing.T) {
	fixturePath := filepath.Join("schemas", "v2", "schema.json")
	jazon, sch := expandThisSchemaOrDieTrying(t, fixturePath)

	// expansion leaves some circular $ref behind: let's assert those

	// assert all $ref match
	// "$ref": "#/definitions/something"
	assertRefInJSON(t, jazon, "#/definitions/")

	// verify that all $ref resolve in the new root schema
	assertRefExpand(t, jazon, "", sch)
}

func TestExpand_ContinueOnError(t *testing.T) {
	specPath := filepath.Join("fixtures", "expansion", "missingRef.json")

	defer log.SetOutput(os.Stdout)
	log.SetOutput(io.Discard)

	// missing $ref in spec
	missingRefDoc, err := jsonDoc(specPath)
	require.NoError(t, err)

	testCase := struct {
		Input    *Swagger `json:"input"`
		Expected *Swagger `json:"expected"`
	}{}
	require.NoError(t, json.Unmarshal(missingRefDoc, &testCase))

	opts := &ExpandOptions{
		ContinueOnError: true,
		RelativeBase:    specPath,
	}
	require.NoError(t, ExpandSpec(testCase.Input, opts))

	assert.Equal(t, testCase.Expected, testCase.Input, "Should continue expanding spec when a definition can't be found.")

	// missing $ref in items
	doc, err := jsonDoc("fixtures/expansion/missingItemRef.json")
	require.NoError(t, err)

	spec := new(Swagger)
	require.NoError(t, json.Unmarshal(doc, spec))

	assert.NotPanics(t, func() {
		require.NoError(t, ExpandSpec(spec, opts))
	}, "Array of missing refs should not cause a panic, and continue to expand spec.")
}

func TestExpand_InternalSchemas2(t *testing.T) {
	basePath := normalizeBase(filepath.Join("fixtures", "expansion", "schemas2.json"))

	carsDoc, err := jsonDoc(basePath)
	require.NoError(t, err)

	spec := new(Swagger)
	require.NoError(t, json.Unmarshal(carsDoc, spec))

	resolver := defaultSchemaLoader(spec, nil, nil, nil)

	// verify unmarshaled structure
	schema := spec.Definitions["car"]
	oldBrand := schema.Properties["brand"]
	require.NotEmpty(t, oldBrand.Items.Schema.Ref.String()) // this is a $ref
	require.NotEqual(t, spec.Definitions["brand"], oldBrand)

	_, err = expandSchema(schema, []string{"#/definitions/car"}, resolver, basePath)
	require.NoError(t, err)

	// verify expanded schema for Car, in the document passed
	newBrand := schema.Properties["brand"]
	assert.Empty(t, newBrand.Items.Schema.Ref.String())
	assert.Equal(t, spec.Definitions["brand"], *newBrand.Items.Schema)

	// verify expanded schema for Truck, in the returned schema
	schema = spec.Definitions["truck"]
	require.NotEmpty(t, schema.Items.Schema.Ref.String())
	s, err := expandSchema(schema, []string{"#/definitions/truck"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.Items.Schema.Ref.String()) // no more a $ref
	assert.False(t, schema.Items.Schema.Ref.IsRoot()) // no more a $ref
	assert.Equal(t, spec.Definitions["car"], *schema.Items.Schema)

	sch := new(Schema)
	_, err = expandSchema(*sch, []string{""}, resolver, basePath)
	require.NoError(t, err)

	// verify expanded schema for Batch, in the returned schema
	schema = spec.Definitions["batch"]
	s, err = expandSchema(schema, []string{"#/definitions/batch"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.Items.Schema.Items.Schema.Ref.String())
	assert.Equal(t, *schema.Items.Schema.Items.Schema, spec.Definitions["brand"])

	// verify expanded schema for Batch2, in the returned schema
	schema = spec.Definitions["batch2"]
	s, err = expandSchema(schema, []string{"#/definitions/batch2"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.Items.Schemas[0].Items.Schema.Ref.String())
	assert.Empty(t, schema.Items.Schemas[1].Items.Schema.Ref.String())
	assert.Equal(t, *schema.Items.Schemas[0].Items.Schema, spec.Definitions["brand"])
	assert.Equal(t, *schema.Items.Schemas[1].Items.Schema, spec.Definitions["tag"])

	// verify expanded schema for AllOfBoth, in the returned schema [expand allOf]
	schema = spec.Definitions["allofBoth"]
	s, err = expandSchema(schema, []string{"#/definitions/allofBoth"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.AllOf[0].Items.Schema.Ref.String())
	assert.Empty(t, schema.AllOf[1].Items.Schema.Ref.String())
	assert.Equal(t, *schema.AllOf[0].Items.Schema, spec.Definitions["brand"])
	assert.Equal(t, *schema.AllOf[1].Items.Schema, spec.Definitions["tag"])

	// verify expanded schema for AnyOfBoth, in the returned schema [expand anyOf]
	schema = spec.Definitions["anyofBoth"]
	s, err = expandSchema(schema, []string{"#/definitions/anyofBoth"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.AnyOf[0].Items.Schema.Ref.String())
	assert.Empty(t, schema.AnyOf[1].Items.Schema.Ref.String())
	assert.Equal(t, *schema.AnyOf[0].Items.Schema, spec.Definitions["brand"])
	assert.Equal(t, *schema.AnyOf[1].Items.Schema, spec.Definitions["tag"])

	// verify expanded schema for OneOfBoth, in the returned schema [expand oneOf]
	schema = spec.Definitions["oneofBoth"]
	s, err = expandSchema(schema, []string{"#/definitions/oneofBoth"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.OneOf[0].Items.Schema.Ref.String())
	assert.Empty(t, schema.OneOf[1].Items.Schema.Ref.String())
	assert.Equal(t, *schema.OneOf[0].Items.Schema, spec.Definitions["brand"])
	assert.Equal(t, *schema.OneOf[1].Items.Schema, spec.Definitions["tag"])

	// verify expanded schema for NotSomething, in the returned schema [expand not]
	schema = spec.Definitions["notSomething"]
	s, err = expandSchema(schema, []string{"#/definitions/notSomething"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.Not.Items.Schema.Ref.String())
	assert.Equal(t, *schema.Not.Items.Schema, spec.Definitions["tag"])

	// verify expanded schema for WithAdditional, in the returned schema [expand additionalProperties]
	schema = spec.Definitions["withAdditional"]
	s, err = expandSchema(schema, []string{"#/definitions/withAdditional"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.AdditionalProperties.Schema.Items.Schema.Ref.String())
	assert.Equal(t, *schema.AdditionalProperties.Schema.Items.Schema, spec.Definitions["tag"])

	// verify expanded schema for WithAdditionalItems, in the returned schema [expand additionalItems]
	schema = spec.Definitions["withAdditionalItems"]
	s, err = expandSchema(schema, []string{"#/definitions/withAdditionalItems"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.AdditionalItems.Schema.Items.Schema.Ref.String())
	assert.Equal(t, *schema.AdditionalItems.Schema.Items.Schema, spec.Definitions["tag"])

	// verify expanded schema for WithPattern, in the returned schema [expand PatternProperties]
	schema = spec.Definitions["withPattern"]
	s, err = expandSchema(schema, []string{"#/definitions/withPattern"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	prop := schema.PatternProperties["^x-ab"]
	assert.Empty(t, prop.Items.Schema.Ref.String())
	assert.Equal(t, *prop.Items.Schema, spec.Definitions["tag"])

	// verify expanded schema for Deps, in the returned schema [expand dependencies]
	schema = spec.Definitions["deps"]
	s, err = expandSchema(schema, []string{"#/definitions/deps"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	prop2 := schema.Dependencies["something"]
	assert.Empty(t, prop2.Schema.Items.Schema.Ref.String())
	assert.Equal(t, *prop2.Schema.Items.Schema, spec.Definitions["tag"])

	// verify expanded schema for Defined, in the returned schema [expand nested definitions]
	schema = spec.Definitions["defined"]
	s, err = expandSchema(schema, []string{"#/definitions/defined"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	prop = schema.Definitions["something"]
	assert.Empty(t, prop.Items.Schema.Ref.String())
	assert.Equal(t, *prop.Items.Schema, spec.Definitions["tag"])
}

func TestExpand_InternalSchemas1(t *testing.T) {
	basePath := normalizeBase(filepath.Join("fixtures", "expansion", "schemas1.json"))

	carsDoc, err := jsonDoc(basePath)
	require.NoError(t, err)

	spec := new(Swagger)
	require.NoError(t, json.Unmarshal(carsDoc, spec))

	resolver := defaultSchemaLoader(spec, nil, nil, nil)

	schema := spec.Definitions["car"]
	oldBrand := schema.Properties["brand"]
	assert.NotEmpty(t, oldBrand.Ref.String())
	assert.NotEqual(t, spec.Definitions["brand"], oldBrand)

	s, err := expandSchema(schema, []string{"#/definitions/car"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s

	newBrand := schema.Properties["brand"]
	assert.Empty(t, newBrand.Ref.String())
	assert.Equal(t, spec.Definitions["brand"], newBrand)

	schema = spec.Definitions["truck"]
	assert.NotEmpty(t, schema.Ref.String())

	s, err = expandSchema(schema, []string{"#/definitions/truck"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.Ref.String())
	assert.Equal(t, spec.Definitions["car"], schema)

	sch := new(Schema)
	_, err = expandSchema(*sch, []string{""}, resolver, basePath)
	require.NoError(t, err)

	schema = spec.Definitions["batch"]
	s, err = expandSchema(schema, []string{"#/definitions/batch"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.Items.Schema.Ref.String())
	assert.Equal(t, *schema.Items.Schema, spec.Definitions["brand"])

	schema = spec.Definitions["batch2"]
	s, err = expandSchema(schema, []string{"#/definitions/batch2"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.Items.Schemas[0].Ref.String())
	assert.Empty(t, schema.Items.Schemas[1].Ref.String())
	assert.Equal(t, schema.Items.Schemas[0], spec.Definitions["brand"])
	assert.Equal(t, schema.Items.Schemas[1], spec.Definitions["tag"])

	schema = spec.Definitions["allofBoth"]
	s, err = expandSchema(schema, []string{"#/definitions/allofBoth"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.AllOf[0].Ref.String())
	assert.Empty(t, schema.AllOf[1].Ref.String())
	assert.Equal(t, schema.AllOf[0], spec.Definitions["brand"])
	assert.Equal(t, schema.AllOf[1], spec.Definitions["tag"])

	schema = spec.Definitions["anyofBoth"]
	s, err = expandSchema(schema, []string{"#/definitions/anyofBoth"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.AnyOf[0].Ref.String())
	assert.Empty(t, schema.AnyOf[1].Ref.String())
	assert.Equal(t, schema.AnyOf[0], spec.Definitions["brand"])
	assert.Equal(t, schema.AnyOf[1], spec.Definitions["tag"])

	schema = spec.Definitions["oneofBoth"]
	s, err = expandSchema(schema, []string{"#/definitions/oneofBoth"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.OneOf[0].Ref.String())
	assert.Empty(t, schema.OneOf[1].Ref.String())
	assert.Equal(t, schema.OneOf[0], spec.Definitions["brand"])
	assert.Equal(t, schema.OneOf[1], spec.Definitions["tag"])

	schema = spec.Definitions["notSomething"]
	s, err = expandSchema(schema, []string{"#/definitions/notSomething"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.Not.Ref.String())
	assert.Equal(t, *schema.Not, spec.Definitions["tag"])

	schema = spec.Definitions["withAdditional"]
	s, err = expandSchema(schema, []string{"#/definitions/withAdditional"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.AdditionalProperties.Schema.Ref.String())
	assert.Equal(t, *schema.AdditionalProperties.Schema, spec.Definitions["tag"])

	schema = spec.Definitions["withAdditionalItems"]
	s, err = expandSchema(schema, []string{"#/definitions/withAdditionalItems"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	assert.Empty(t, schema.AdditionalItems.Schema.Ref.String())
	assert.Equal(t, *schema.AdditionalItems.Schema, spec.Definitions["tag"])

	schema = spec.Definitions["withPattern"]
	s, err = expandSchema(schema, []string{"#/definitions/withPattern"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	prop := schema.PatternProperties["^x-ab"]
	assert.Empty(t, prop.Ref.String())
	assert.Equal(t, prop, spec.Definitions["tag"])

	schema = spec.Definitions["deps"]
	s, err = expandSchema(schema, []string{"#/definitions/deps"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	prop2 := schema.Dependencies["something"]
	assert.Empty(t, prop2.Schema.Ref.String())
	assert.Equal(t, *prop2.Schema, spec.Definitions["tag"])

	schema = spec.Definitions["defined"]
	s, err = expandSchema(schema, []string{"#/definitions/defined"}, resolver, basePath)
	require.NoError(t, err)
	require.NotNil(t, s)

	schema = *s
	prop = schema.Definitions["something"]
	assert.Empty(t, prop.Ref.String())
	assert.Equal(t, prop, spec.Definitions["tag"])
}

func TestExpand_RelativeBaseURI(t *testing.T) {
	server := httptest.NewServer(http.FileServer(http.Dir("fixtures/remote")))
	defer server.Close()

	spec := new(Swagger)

	require.NoError(t, ExpandSpec(spec, nil))

	// this a spec on local fs...
	specDoc, err := jsonDoc("fixtures/remote/all-the-things.json")
	require.NoError(t, err)

	spec = new(Swagger)
	require.NoError(t, json.Unmarshal(specDoc, spec))

	pet := spec.Definitions["pet"]
	errorModel := spec.Definitions["errorModel"]
	petResponse := spec.Responses["petResponse"]
	petResponse.Schema = &pet
	stringResponse := spec.Responses["stringResponse"]
	tagParam := spec.Parameters["tag"]
	idParam := spec.Parameters["idParam"]
	anotherPet := spec.Responses["anotherPet"]

	// ... with some $ref with http scheme
	anotherPet.Ref = MustCreateRef(server.URL + "/" + anotherPet.Ref.String())

	opts := &ExpandOptions{
		RelativeBase: server.URL + "/all-the-things.json",
	}

	require.NoError(t, ExpandResponse(&anotherPet, opts.RelativeBase))

	spec.Responses["anotherPet"] = anotherPet

	circularA := spec.Responses["circularA"]
	circularA.Ref = MustCreateRef(server.URL + "/" + circularA.Ref.String())

	require.NoError(t, ExpandResponse(&circularA, opts.RelativeBase))

	// circularA is self-referencing: results in an empty response: this is okay and expected
	// for the expand use case. The flatten use case should however be expected to fail on this.
	assert.Empty(t, circularA.Description)
	assert.Empty(t, circularA.Ref)

	spec.Responses["circularA"] = circularA

	// expand again, no issue should arise
	require.NoError(t, ExpandSpec(spec, opts))

	// backRef navigates back to the root document (relative $ref)
	backRef := spec.Responses["backRef"]
	require.NoError(t, ExpandResponse(&backRef, opts.RelativeBase))
	assert.Equal(t, "pet response", backRef.Description)
	assert.NotEmpty(t, backRef.Schema)
	assert.Empty(t, backRef.Ref)

	assert.Equal(t, tagParam, spec.Parameters["query"])

	assert.Equal(t, petResponse, spec.Responses["petResponse"])
	assert.Equal(t, petResponse, spec.Responses["anotherPet"])
	assert.Equal(t, petResponse, spec.Paths.Paths["/pets"].Post.Responses.StatusCodeResponses[200])
	assert.Equal(t, petResponse, spec.Paths.Paths["/"].Get.Responses.StatusCodeResponses[200])

	assert.Equal(t, pet, *spec.Paths.Paths["/pets"].Get.Responses.StatusCodeResponses[200].Schema.Items.Schema)
	assert.Equal(t, pet, *spec.Responses["petResponse"].Schema)
	assert.Equal(t, pet, spec.Definitions["petInput"].AllOf[0])

	assert.Equal(t, spec.Definitions["petInput"], *spec.Paths.Paths["/pets"].Post.Parameters[0].Schema)

	assert.Equal(t, stringResponse, *spec.Paths.Paths["/"].Get.Responses.Default)

	assert.Equal(t, errorModel, *spec.Paths.Paths["/pets"].Get.Responses.Default.Schema)
	assert.Equal(t, errorModel, *spec.Paths.Paths["/pets"].Post.Responses.Default.Schema)

	pi := spec.Paths.Paths["/pets/{id}"]
	assert.Equal(t, idParam, pi.Get.Parameters[0])
	assert.Equal(t, petResponse, pi.Get.Responses.StatusCodeResponses[200])
	assert.Equal(t, idParam, pi.Delete.Parameters[0])

	assert.Equal(t, errorModel, *pi.Get.Responses.Default.Schema)
	assert.Equal(t, errorModel, *pi.Delete.Responses.Default.Schema)
}

func resolutionContextServer() *httptest.Server {
	var servedAt string
	server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		if req.URL.Path == "/resolution.json" {

			b, _ := os.ReadFile(filepath.Join(specs, "resolution.json"))
			var ctnt map[string]interface{}
			_ = json.Unmarshal(b, &ctnt)
			ctnt["id"] = servedAt

			rw.Header().Set("Content-Type", "application/json")
			rw.WriteHeader(http.StatusOK)
			bb, _ := json.Marshal(ctnt)
			_, _ = rw.Write(bb)
			return
		}
		if req.URL.Path == "/resolution2.json" {
			b, _ := os.ReadFile(filepath.Join(specs, "resolution2.json"))
			var ctnt map[string]interface{}
			_ = json.Unmarshal(b, &ctnt)
			ctnt["id"] = servedAt

			rw.Header().Set("Content-Type", "application/json")
			bb, _ := json.Marshal(ctnt)
			_, _ = rw.Write(bb)
			return
		}

		if req.URL.Path == "/boolProp.json" {
			rw.Header().Set("Content-Type", "application/json")
			b, _ := json.Marshal(map[string]interface{}{
				"type": "boolean",
			})
			_, _ = rw.Write(b)
			return
		}

		if req.URL.Path == "/deeper/stringProp.json" {
			rw.Header().Set("Content-Type", "application/json")
			b, _ := json.Marshal(map[string]interface{}{
				"type": "string",
			})
			_, _ = rw.Write(b)
			return
		}

		if req.URL.Path == "/deeper/arrayProp.json" {
			rw.Header().Set("Content-Type", "application/json")
			b, _ := json.Marshal(map[string]interface{}{
				"type": "array",
				"items": map[string]interface{}{
					"type": "file",
				},
			})
			_, _ = rw.Write(b)
			return
		}

		rw.WriteHeader(http.StatusNotFound)
	}))
	servedAt = server.URL
	return server
}

func TestExpand_RemoteRefWithResolutionContext(t *testing.T) {
	server := resolutionContextServer()
	defer server.Close()

	tgt := RefSchema(server.URL + "/resolution.json#/definitions/bool")
	require.NoError(t, ExpandSchema(tgt, nil, nil))

	assert.Equal(t, StringOrArray([]string{"boolean"}), tgt.Type)
	assert.Empty(t, tgt.Ref)
}

func TestExpandRemoteRef_WithNestedResolutionContext(t *testing.T) {
	server := resolutionContextServer()
	defer server.Close()

	tgt := RefSchema(server.URL + "/resolution.json#/items")
	require.NoError(t, ExpandSchema(tgt, nil, nil))

	require.Empty(t, tgt.Ref)
	require.NotNil(t, tgt.Items)
	require.NotNil(t, tgt.Schema)
	assert.Equal(t, "deeper/", tgt.ID) // schema id is preserved

	assert.Equal(t, StringOrArray([]string{"string"}), tgt.Items.Schema.Type)
	assert.Empty(t, tgt.Items.Schema.Ref)
}

/*
   This next test will have to wait until we do full $ID analysis for every subschema on every file that is referenced
// For now, TestExpandRemoteRef_WithNestedResolutionContext replaces this next test
func TestExpandRemoteRef_WithNestedResolutionContext_WithParentID(t *testing.T) {
 	server := resolutionContextServer()
 	defer server.Close()

	tgt := RefSchema(server.URL + "/resolution.json#/items/items")
	require.NoError(t, ExpandSchema(tgt, nil, nil))
	t.Logf("%s", asJSON(t, tgt))

	assert.Equal(t, StringOrArray([]string{"string"}), tgt.Type)
}
*/

func TestExpand_RemoteRefWithNestedResolutionContextWithFragment(t *testing.T) {
	server := resolutionContextServer()
	defer server.Close()

	tgt := RefSchema(server.URL + "/resolution2.json#/items")
	require.NoError(t, ExpandSchema(tgt, nil, nil))

	require.Empty(t, tgt.Ref)
	require.NotNil(t, tgt.Items)
	require.NotNil(t, tgt.Schema)
	assert.Equal(t, "deeper/", tgt.ID) // schema id is preserved

	assert.Equal(t, StringOrArray([]string{"file"}), tgt.Items.Schema.Type)
	assert.Empty(t, tgt.Items.Schema.Ref)
}

func TestExpand_TransitiveRefs(t *testing.T) {
	basePath := filepath.Join(specs, "todos.json")

	rawSpec, err := os.ReadFile(basePath)
	require.NoError(t, err)

	var spec *Swagger
	require.NoError(t, json.Unmarshal(rawSpec, &spec))

	opts := &ExpandOptions{
		RelativeBase: basePath,
	}

	require.NoError(t, ExpandSpec(spec, opts))

	assert.Equal(t, "todos.stoplight.io", spec.Host) // i.e. not empty
	jazon := asJSON(t, spec)

	// verify that the spec has been fully expanded
	assertNoRef(t, jazon)
}

func TestExpand_SchemaWithRoot(t *testing.T) {
	root := new(Swagger)
	require.NoError(t, json.Unmarshal(PetStoreJSONMessage, root))

	// 1. remove ID from root definition
	origPet := root.Definitions["Pet"]
	newPet := origPet
	newPet.ID = ""
	root.Definitions["Pet"] = newPet
	expandRootWithID(t, root, withoutSchemaID)

	// 2. put back ID in Pet definition
	// nested $ref should fail
	root.Definitions["Pet"] = origPet
	expandRootWithID(t, root, withSchemaID)
}

func expandRootWithID(t testing.TB, root *Swagger, testcase string) {
	t.Logf("case: expanding $ref to schema without ID, with nested $ref with %s ID", testcase)
	sch := RefSchema("#/definitions/newPet")
	err := ExpandSchema(sch, root, nil)

	if testcase == withSchemaID {
		require.Errorf(t, err, "expected %s NOT to expand properly because of the ID in the parent schema", sch.Ref.String())
	} else {
		require.NoErrorf(t, err, "expected %s to expand properly", sch.Ref.String())

		// verify that the spec has been fully expanded
		jazon := asJSON(t, sch)
		assertNoRef(t, jazon)
	}

	t.Log("case: expanding $ref to schema without nested $ref")
	sch = RefSchema("#/definitions/Category")
	require.NoErrorf(t, ExpandSchema(sch, root, nil), "expected %s to expand properly", sch.Ref.String())

	t.Logf("case: expanding $ref to schema with %s ID and nested $ref", testcase)
	sch = RefSchema("#/definitions/Pet")
	err = ExpandSchema(sch, root, nil)

	if testcase == withSchemaID {
		require.Errorf(t, err, "expected %s NOT to expand properly because of the ID in the parent schema", sch.Ref.String())
	} else {
		require.NoErrorf(t, err, "expected %s to expand properly", sch.Ref.String())

		// verify that the spec has been fully expanded
		jazon := asJSON(t, sch)
		assertNoRef(t, jazon)
	}
}

func TestExpand_PathItem(t *testing.T) {
	jazon, _ := expandThisOrDieTrying(t, pathItemsFixture)
	assert.JSONEq(t, `{
         "swagger": "2.0",
         "info": {
          "title": "PathItems refs",
          "version": "1.0"
         },
         "paths": {
          "/todos": {
           "get": {
            "responses": {
             "200": {
              "description": "List Todos",
              "schema": {
               "type": "array",
               "items": {
                "type": "string"
               }
              }
             },
             "404": {
              "description": "error"
             }
            }
           }
          }
         }
			 }`, jazon)
}

func TestExpand_ExtraItems(t *testing.T) {
	jazon, _ := expandThisOrDieTrying(t, extraRefFixture)
	assert.JSONEq(t, `{
         "schemes": [
          "http"
         ],
         "swagger": "2.0",
         "info": {
          "title": "Supported, but non Swagger 20 compliant $ref constructs",
          "version": "2.1.0"
         },
         "host": "item.com",
         "basePath": "/extraRefs",
         "paths": {
          "/employees": {
           "get": {
            "summary": "List Employee Types",
            "operationId": "LIST-Employees",
            "parameters": [
             {
							"description": "unsupported $ref in simple param",
              "type": "array",
              "items": {
               "$ref": "#/definitions/arrayType"
              },
              "name": "myQueryParam",
              "in": "query"
             }
            ],
            "responses": {
             "200": {
							"description": "unsupported $ref in header",
              "schema": {
               "type": "string"
              },
              "headers": {
               "X-header": {
                  "type": "array",
                  "items": {
                    "$ref": "#/definitions/headerType"
                  }
							  }
              }
             }
            }
           }
          }
         },
         "definitions": {
          "arrayType": {
           "type": "integer",
           "format": "int32"
          },
          "headerType": {
           "type": "string",
           "format": "uuid"
          }
         }
			 }`, jazon)
}

func TestExpand_Issue145(t *testing.T) {
	cwd, err := os.Getwd()
	require.NoError(t, err)
	pseudoRoot := normalizeBase(filepath.Join(cwd, rootBase))

	// assert the internal behavior of baseForRoot()
	t.Run("with nil root, empty cache", func(t *testing.T) {
		cache := defaultResolutionCache()
		require.Equal(t, pseudoRoot, baseForRoot(nil, cache))

		t.Run("empty root is cached", func(t *testing.T) {
			value, ok := cache.Get(pseudoRoot)
			require.True(t, ok) // found in cache
			asMap, ok := value.(map[string]interface{})
			require.True(t, ok)
			require.Empty(t, asMap)
		})
	})

	t.Run("with non-nil root, empty cache", func(t *testing.T) {
		cache := defaultResolutionCache()
		require.Equal(t, pseudoRoot, baseForRoot(map[string]interface{}{"key": "arbitrary"}, cache))

		t.Run("non-empty root is cached", func(t *testing.T) {
			value, ok := cache.Get(pseudoRoot)
			require.True(t, ok) // found in cache
			asMap, ok := value.(map[string]interface{})
			require.True(t, ok)
			require.Contains(t, asMap, "key")
			require.Equal(t, "arbitrary", asMap["key"])
		})

		t.Run("with nil root, non-empty cache", func(t *testing.T) {
			require.Equal(t, pseudoRoot, baseForRoot(nil, cache))

			t.Run("non-empty root is kept", func(t *testing.T) {
				value, ok := cache.Get(pseudoRoot)
				require.True(t, ok) // found in cache
				asMap, ok := value.(map[string]interface{})
				require.True(t, ok)
				require.Contains(t, asMap, "key")
				require.Equal(t, "arbitrary", asMap["key"])
			})
		})
	})
}

// PetStore20 json doc for swagger 2.0 pet store
const PetStore20 = `{
  "swagger": "2.0",
  "info": {
    "version": "1.0.0",
    "title": "Swagger Petstore",
    "contact": {
      "name": "Wordnik API Team",
      "url": "http://developer.wordnik.com"
    },
    "license": {
      "name": "Creative Commons 4.0 International",
      "url": "http://creativecommons.org/licenses/by/4.0/"
    }
  },
  "host": "petstore.swagger.wordnik.com",
  "basePath": "/api",
  "schemes": [
    "http"
  ],
  "paths": {
    "/pets": {
      "get": {
        "security": [
          {
            "basic": []
          }
        ],
        "tags": [ "Pet Operations" ],
        "operationId": "getAllPets",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "description": "The status to filter by",
            "type": "string"
          },
          {
            "name": "limit",
            "in": "query",
            "description": "The maximum number of results to return",
            "type": "integer",
						"format": "int64"
          }
        ],
        "summary": "Finds all pets in the system",
        "responses": {
          "200": {
            "description": "Pet response",
            "schema": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/Pet"
              }
            }
          },
          "default": {
            "description": "Unexpected error",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          }
        }
      },
      "post": {
        "security": [
          {
            "basic": []
          }
        ],
        "tags": [ "Pet Operations" ],
        "operationId": "createPet",
        "summary": "Creates a new pet",
        "consumes": ["application/x-yaml"],
        "produces": ["application/x-yaml"],
        "parameters": [
          {
            "name": "pet",
            "in": "body",
            "description": "The Pet to create",
            "required": true,
            "schema": {
              "$ref": "#/definitions/newPet"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Created Pet response",
            "schema": {
              "$ref": "#/definitions/Pet"
            }
          },
          "default": {
            "description": "Unexpected error",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          }
        }
      }
    },
    "/pets/{id}": {
      "delete": {
        "security": [
          {
            "apiKey": []
          }
        ],
        "description": "Deletes the Pet by id",
        "operationId": "deletePet",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "ID of pet to delete",
            "required": true,
            "type": "integer",
            "format": "int64"
          }
        ],
        "responses": {
          "204": {
            "description": "pet deleted"
          },
          "default": {
            "description": "unexpected error",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          }
        }
      },
      "get": {
        "tags": [ "Pet Operations" ],
        "operationId": "getPetById",
        "summary": "Finds the pet by id",
        "responses": {
          "200": {
            "description": "Pet response",
            "schema": {
              "$ref": "#/definitions/Pet"
            }
          },
          "default": {
            "description": "Unexpected error",
            "schema": {
              "$ref": "#/definitions/Error"
            }
          }
        }
      },
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "description": "ID of pet",
          "required": true,
          "type": "integer",
          "format": "int64"
        }
      ]
    }
  },
  "definitions": {
    "Category": {
      "id": "Category",
      "properties": {
        "id": {
          "format": "int64",
          "type": "integer"
        },
        "name": {
          "type": "string"
        }
      }
    },
    "Pet": {
      "id": "Pet",
      "properties": {
        "category": {
          "$ref": "#/definitions/Category"
        },
        "id": {
          "description": "unique identifier for the pet",
          "format": "int64",
          "maximum": 100.0,
          "minimum": 0.0,
          "type": "integer"
        },
        "name": {
          "type": "string"
        },
        "photoUrls": {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "status": {
          "description": "pet status in the store",
          "enum": [
            "available",
            "pending",
            "sold"
          ],
          "type": "string"
        },
        "tags": {
          "items": {
            "$ref": "#/definitions/Tag"
          },
          "type": "array"
        }
      },
      "required": [
        "id",
        "name"
      ]
    },
    "newPet": {
      "anyOf": [
        {
          "$ref": "#/definitions/Pet"
        },
        {
          "required": [
            "name"
          ]
        }
      ]
    },
    "Tag": {
      "id": "Tag",
      "properties": {
        "id": {
          "format": "int64",
          "type": "integer"
        },
        "name": {
          "type": "string"
        }
      }
    },
    "Error": {
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "format": "int32"
        },
        "message": {
          "type": "string"
        }
      }
    }
  },
  "consumes": [
    "application/json",
    "application/xml"
  ],
  "produces": [
    "application/json",
    "application/xml",
    "text/plain",
    "text/html"
  ],
  "securityDefinitions": {
    "basic": {
      "type": "basic"
    },
    "apiKey": {
      "type": "apiKey",
      "in": "header",
      "name": "X-API-KEY"
    }
  }
}
`