// Copyright (c) quickfixengine.org  All rights reserved.
//
// This file may be distributed under the terms of the quickfixengine.org
// license as defined by quickfixengine.org and appearing in the file
// LICENSE included in the packaging of this file.
//
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE.
//
// See http://www.quickfixengine.org/LICENSE for licensing information.
//
// Contact ask@quickfixengine.org if any conditions of this licensing
// are not clear to you.

package quickfix

import (
	"bytes"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"

	"quantex.com/qfixdpl/quickfix/datadictionary"
)

type validateTest struct {
	TestName             string
	Validator            Validator
	MessageBytes         []byte
	ExpectedRejectReason int
	ExpectedRefTagID     *Tag
	DoNotExpectReject    bool
}

func TestValidate(t *testing.T) {
	var tests = []validateTest{
		tcInvalidTagNumberHeader(),
		tcInvalidTagNumberHeaderFixT(),
		tcInvalidTagNumberBody(),
		tcInvalidTagNumberBodyFixT(),
		tcInvalidTagNumberTrailer(),
		tcInvalidTagNumberTrailerFixT(),
		tcTagSpecifiedWithoutAValue(),
		tcTagSpecifiedWithoutAValueFixT(),
		tcTagSpecifiedWithoutAValueValidateHasValues(),
		tcTagSpecifiedWithoutAValueFixTValidateHasValues(),
		tcTagSpecifiedWithoutAValueValidateHasValuesNoDataDictionary(),
		tcInvalidMsgType(),
		tcInvalidMsgTypeFixT(),
		tcValueIsIncorrect(),
		tcValueIsIncorrectFixT(),
		tcIncorrectDataFormatForValue(),
		tcIncorrectDataFormatForValueFixT(),
		tcTagSpecifiedOutOfRequiredOrderHeader(),
		tcTagSpecifiedOutOfRequiredOrderHeaderFixT(),
		tcTagSpecifiedOutOfRequiredOrderTrailer(),
		tcTagSpecifiedOutOfRequiredOrderTrailerFixT(),
		tcTagSpecifiedOutOfRequiredOrderDisabledHeader(),
		tcTagSpecifiedOutOfRequiredOrderDisabledHeaderFixT(),
		tcTagSpecifiedOutOfRequiredOrderDisabledTrailer(),
		tcTagSpecifiedOutOfRequiredOrderDisabledTrailerFixT(),
		tcTagAppearsMoreThanOnce(),
		tcTagAppearsMoreThanOnceFixT(),
		tcFloatValidation(),
		tcFloatValidationFixT(),
		tcTagNotDefinedForMessage(),
		tcTagNotDefinedForMessageFixT(),
		tcTagIsDefinedForMessage(),
		tcTagIsDefinedForMessageFixT(),
		tcFieldNotFoundBody(),
		tcFieldNotFoundBodyFixT(),
		tcFieldNotFoundHeader(),
		tcFieldNotFoundHeaderFixT(),
		tcInvalidTagCheckDisabled(),
		tcInvalidTagCheckDisabledFixT(),
		tcInvalidTagCheckEnabled(),
		tcInvalidTagCheckEnabledFixT(),
		tcAllowUnknownMessageFieldsEnabled(),
		tcAllowUnknownMessageFieldsEnabledFixT(),
		tcAllowUnknownMessageFieldsDisabled(),
		tcAllowUnknownMessageFieldsDisabledFixT(),
		tcCheckUserDefinedFieldsEnabled(),
		tcCheckUserDefinedFieldsEnabledFixT(),
		tcCheckUserDefinedFieldsDisabled(),
		tcCheckUserDefinedFieldsDisabledFixT(),
		tcMultipleRepeatingGroupFields(),
	}

	msg := NewMessage()
	for _, test := range tests {
		assert.Nil(t, ParseMessage(msg, bytes.NewBuffer(test.MessageBytes)))
		reject := test.Validator.Validate(msg)

		switch {
		case reject == nil && test.DoNotExpectReject:
			continue

		case reject != nil && test.DoNotExpectReject:
			t.Errorf("%v: Unexpected reject: %v", test.TestName, reject)
			continue

		case reject == nil:
			t.Errorf("%v: Expected reject", test.TestName)
			continue
		}

		if reject.RejectReason() != test.ExpectedRejectReason {
			t.Errorf("%v: Expected reason %v got %v", test.TestName, test.ExpectedRejectReason, reject.RejectReason())
		}

		switch {
		case reject.RefTagID() == nil && test.ExpectedRefTagID == nil:
		// OK, expected and actual ref tag not set.
		case reject.RefTagID() != nil && test.ExpectedRefTagID == nil:
			t.Errorf("%v: Unexpected RefTag '%v'", test.TestName, *reject.RefTagID())
		case reject.RefTagID() == nil && test.ExpectedRefTagID != nil:
			t.Errorf("%v: Expected RefTag '%v'", test.TestName, *test.ExpectedRefTagID)
		case *reject.RefTagID() == *test.ExpectedRefTagID:
			// OK, tags equal.
		default:
			t.Errorf("%v: Expected RefTag '%v' got '%v'", test.TestName, *test.ExpectedRefTagID, *reject.RefTagID())
		}
	}
}

func createFIX40NewOrderSingle() *Message {
	msg := NewMessage()
	msg.Header.SetField(tagMsgType, FIXString("D"))
	msg.Header.SetField(tagBeginString, FIXString("FIX.4.0"))
	msg.Header.SetField(tagBodyLength, FIXString("0"))
	msg.Header.SetField(tagSenderCompID, FIXString("0"))
	msg.Header.SetField(tagTargetCompID, FIXString("0"))
	msg.Header.SetField(tagMsgSeqNum, FIXString("0"))
	msg.Header.SetField(tagSendingTime, FIXUTCTimestamp{Time: time.Now()})

	msg.Body.SetField(Tag(11), FIXString("A"))
	msg.Body.SetField(Tag(21), FIXString("1"))
	msg.Body.SetField(Tag(55), FIXString("A"))
	msg.Body.SetField(Tag(54), FIXString("1"))
	msg.Body.SetField(Tag(40), FIXString("1"))
	msg.Body.SetField(Tag(38), FIXInt(5))
	msg.Body.SetField(Tag(100), FIXString("0"))

	msg.Trailer.SetField(tagCheckSum, FIXString("000"))

	return msg
}

func createFIX43NewOrderSingle() *Message {
	msg := NewMessage()
	msg.Header.SetField(tagMsgType, FIXString("D"))
	msg.Header.SetField(tagBeginString, FIXString("FIX.4.3"))
	msg.Header.SetField(tagBodyLength, FIXString("0"))
	msg.Header.SetField(tagSenderCompID, FIXString("0"))
	msg.Header.SetField(tagTargetCompID, FIXString("0"))
	msg.Header.SetField(tagMsgSeqNum, FIXString("0"))
	msg.Header.SetField(tagSendingTime, FIXUTCTimestamp{Time: time.Now()})

	msg.Body.SetField(Tag(11), FIXString("A"))
	msg.Body.SetField(Tag(21), FIXString("1"))
	msg.Body.SetField(Tag(55), FIXString("A"))
	msg.Body.SetField(Tag(54), FIXString("1"))
	msg.Body.SetField(Tag(38), FIXInt(5))
	msg.Body.SetField(Tag(40), FIXString("1"))
	msg.Body.SetField(Tag(60), FIXUTCTimestamp{Time: time.Now()})

	msg.Trailer.SetField(tagCheckSum, FIXString("000"))

	return msg
}

func createFIX50SP2NewOrderSingle() *Message {
	msg := NewMessage()
	msg.Header.SetField(tagMsgType, FIXString("D"))
	msg.Header.SetField(tagBeginString, FIXString("FIXT.1.1"))
	msg.Header.SetField(tagBodyLength, FIXString("0"))
	msg.Header.SetField(tagSenderCompID, FIXString("0"))
	msg.Header.SetField(tagTargetCompID, FIXString("0"))
	msg.Header.SetField(tagMsgSeqNum, FIXString("0"))
	msg.Header.SetField(tagSendingTime, FIXUTCTimestamp{Time: time.Now()})

	msg.Body.SetField(Tag(11), FIXString("A"))
	msg.Body.SetField(Tag(21), FIXString("1"))
	msg.Body.SetField(Tag(55), FIXString("A"))
	msg.Body.SetField(Tag(54), FIXString("1"))
	msg.Body.SetField(Tag(40), FIXString("1"))
	msg.Body.SetField(Tag(38), FIXInt(5))
	msg.Body.SetField(Tag(60), FIXUTCTimestamp{Time: time.Now(), Precision: Micros})
	msg.Body.SetField(Tag(100), FIXString("0"))

	msg.Trailer.SetField(tagCheckSum, FIXString("000"))

	return msg
}

func tcInvalidTagNumberHeader() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	validator := NewValidator(defaultValidatorSettings, dict, nil)
	invalidHeaderFieldMessage := createFIX40NewOrderSingle()
	tag := Tag(9999)
	invalidHeaderFieldMessage.Header.SetField(tag, FIXString("hello"))
	msgBytes := invalidHeaderFieldMessage.build()

	return validateTest{
		TestName:             "Invalid Tag Number Header",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonInvalidTagNumber,
		ExpectedRefTagID:     &tag,
	}
}

func tcInvalidTagNumberHeaderFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	validator := NewValidator(defaultValidatorSettings, appDict, tDict)
	invalidHeaderFieldMessage := createFIX50SP2NewOrderSingle()
	tag := Tag(9999)
	invalidHeaderFieldMessage.Header.SetField(tag, FIXString("hello"))
	msgBytes := invalidHeaderFieldMessage.build()

	return validateTest{
		TestName:             "Invalid Tag Number Header FIXT",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonInvalidTagNumber,
		ExpectedRefTagID:     &tag,
	}
}

func tcInvalidTagNumberBody() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	validator := NewValidator(defaultValidatorSettings, dict, nil)
	invalidBodyFieldMessage := createFIX40NewOrderSingle()
	tag := Tag(9999)
	invalidBodyFieldMessage.Body.SetField(tag, FIXString("hello"))
	msgBytes := invalidBodyFieldMessage.build()

	return validateTest{
		TestName:             "Invalid Tag Number Body",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonInvalidTagNumber,
		ExpectedRefTagID:     &tag,
	}
}

func tcInvalidTagNumberBodyFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	validator := NewValidator(defaultValidatorSettings, appDict, tDict)
	invalidBodyFieldMessage := createFIX50SP2NewOrderSingle()
	tag := Tag(9999)
	invalidBodyFieldMessage.Body.SetField(tag, FIXString("hello"))
	msgBytes := invalidBodyFieldMessage.build()

	return validateTest{
		TestName:             "Invalid Tag Number Body FIXT",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonInvalidTagNumber,
		ExpectedRefTagID:     &tag,
	}
}

func tcInvalidTagNumberTrailer() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	validator := NewValidator(defaultValidatorSettings, dict, nil)
	invalidTrailerFieldMessage := createFIX40NewOrderSingle()
	tag := Tag(9999)
	invalidTrailerFieldMessage.Trailer.SetField(tag, FIXString("hello"))
	msgBytes := invalidTrailerFieldMessage.build()

	return validateTest{
		TestName:             "Invalid Tag Number Trailer",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonInvalidTagNumber,
		ExpectedRefTagID:     &tag,
	}
}

func tcInvalidTagNumberTrailerFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	validator := NewValidator(defaultValidatorSettings, appDict, tDict)
	invalidTrailerFieldMessage := createFIX50SP2NewOrderSingle()
	tag := Tag(9999)
	invalidTrailerFieldMessage.Trailer.SetField(tag, FIXString("hello"))
	msgBytes := invalidTrailerFieldMessage.build()

	return validateTest{
		TestName:             "Invalid Tag Number Trailer FIXT",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonInvalidTagNumber,
		ExpectedRefTagID:     &tag,
	}
}

func tcTagNotDefinedForMessage() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	validator := NewValidator(defaultValidatorSettings, dict, nil)
	invalidMsg := createFIX40NewOrderSingle()
	tag := Tag(41)
	invalidMsg.Body.SetField(tag, FIXString("hello"))
	msgBytes := invalidMsg.build()

	return validateTest{
		TestName:             "Tag Not Defined For Message",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonTagNotDefinedForThisMessageType,
		ExpectedRefTagID:     &tag,
	}
}

func tcTagNotDefinedForMessageFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	validator := NewValidator(defaultValidatorSettings, appDict, tDict)
	invalidMsg := createFIX50SP2NewOrderSingle()
	tag := Tag(41)
	invalidMsg.Body.SetField(tag, FIXString("hello"))
	msgBytes := invalidMsg.build()

	return validateTest{
		TestName:             "Tag Not Defined For Message FIXT",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonTagNotDefinedForThisMessageType,
		ExpectedRefTagID:     &tag,
	}
}

func tcTagIsDefinedForMessage() validateTest {
	// Compare to `tcTagIsNotDefinedForMessage`.
	dict, _ := datadictionary.Parse("spec/FIX43.xml")
	validator := NewValidator(defaultValidatorSettings, dict, nil)
	validMsg := createFIX43NewOrderSingle()
	msgBytes := validMsg.build()

	return validateTest{
		TestName:          "TagIsDefinedForMessage",
		Validator:         validator,
		MessageBytes:      msgBytes,
		DoNotExpectReject: true,
	}
}

func tcTagIsDefinedForMessageFixT() validateTest {
	// Compare to `tcTagIsNotDefinedForMessage`.
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	validator := NewValidator(defaultValidatorSettings, appDict, tDict)
	validMsg := createFIX50SP2NewOrderSingle()
	msgBytes := validMsg.build()

	return validateTest{
		TestName:          "TagIsDefinedForMessage FIXT",
		Validator:         validator,
		MessageBytes:      msgBytes,
		DoNotExpectReject: true,
	}
}

func tcFieldNotFoundBody() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	validator := NewValidator(defaultValidatorSettings, dict, nil)
	invalidMsg1 := NewMessage()
	invalidMsg1.Header.SetField(tagMsgType, FIXString("D")).
		SetField(tagBeginString, FIXString("FIX.4.0")).
		SetField(tagBodyLength, FIXString("0")).
		SetField(tagSenderCompID, FIXString("0")).
		SetField(tagTargetCompID, FIXString("0")).
		SetField(tagMsgSeqNum, FIXString("0")).
		SetField(tagSendingTime, FIXUTCTimestamp{Time: time.Now()})
	invalidMsg1.Trailer.SetField(tagCheckSum, FIXString("000"))

	invalidMsg1.Body.SetField(Tag(11), FIXString("A")).
		SetField(Tag(21), FIXString("A")).
		SetField(Tag(55), FIXString("A")).
		SetField(Tag(54), FIXString("A")).
		SetField(Tag(38), FIXString("A"))

	tag := Tag(40)
	// Ord type is required. invalidMsg1.Body.SetField(Tag(40), "A")).
	msgBytes := invalidMsg1.build()

	return validateTest{
		TestName:             "FieldNotFoundBody",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonRequiredTagMissing,
		ExpectedRefTagID:     &tag,
	}
}

func tcFieldNotFoundBodyFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	validator := NewValidator(defaultValidatorSettings, appDict, tDict)
	invalidMsg1 := NewMessage()
	invalidMsg1.Header.SetField(tagMsgType, FIXString("D")).
		SetField(tagBeginString, FIXString("FIXT.1.1")).
		SetField(tagBodyLength, FIXString("0")).
		SetField(tagSenderCompID, FIXString("0")).
		SetField(tagTargetCompID, FIXString("0")).
		SetField(tagMsgSeqNum, FIXString("0")).
		SetField(tagSendingTime, FIXUTCTimestamp{Time: time.Now()})
	invalidMsg1.Trailer.SetField(tagCheckSum, FIXString("000"))

	invalidMsg1.Body.SetField(Tag(11), FIXString("A")).
		SetField(Tag(21), FIXString("A")).
		SetField(Tag(55), FIXString("A")).
		SetField(Tag(54), FIXString("A")).
		SetField(Tag(38), FIXString("A")).
		SetField(Tag(60), FIXUTCTimestamp{Time: time.Now()})

	tag := Tag(40)
	// Ord type is required. invalidMsg1.Body.SetField(Tag(40), "A")).
	msgBytes := invalidMsg1.build()

	return validateTest{
		TestName:             "FieldNotFoundBody FIXT",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonRequiredTagMissing,
		ExpectedRefTagID:     &tag,
	}
}

func tcFieldNotFoundHeader() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	validator := NewValidator(defaultValidatorSettings, dict, nil)

	invalidMsg2 := NewMessage()
	invalidMsg2.Trailer.SetField(tagCheckSum, FIXString("000"))
	invalidMsg2.Body.SetField(Tag(11), FIXString("A")).
		SetField(Tag(21), FIXString("A")).
		SetField(Tag(55), FIXString("A")).
		SetField(Tag(54), FIXString("A")).
		SetField(Tag(38), FIXString("A"))

	invalidMsg2.Header.SetField(tagMsgType, FIXString("D")).
		SetField(tagBeginString, FIXString("FIX.4.0")).
		SetField(tagBodyLength, FIXString("0")).
		SetField(tagSenderCompID, FIXString("0")).
		SetField(tagTargetCompID, FIXString("0")).
		SetField(tagMsgSeqNum, FIXString("0"))

	// Sending time is required. invalidMsg2.Header.FieldMap.SetField(tag.SendingTime, "0")).
	tag := tagSendingTime
	msgBytes := invalidMsg2.build()

	return validateTest{
		TestName:             "FieldNotFoundHeader",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonRequiredTagMissing,
		ExpectedRefTagID:     &tag,
	}
}

func tcFieldNotFoundHeaderFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	validator := NewValidator(defaultValidatorSettings, appDict, tDict)

	invalidMsg2 := NewMessage()
	invalidMsg2.Trailer.SetField(tagCheckSum, FIXString("000"))
	invalidMsg2.Body.SetField(Tag(11), FIXString("A")).
		SetField(Tag(21), FIXString("A")).
		SetField(Tag(55), FIXString("A")).
		SetField(Tag(54), FIXString("A")).
		SetField(Tag(38), FIXString("A"))

	invalidMsg2.Header.SetField(tagMsgType, FIXString("D")).
		SetField(tagBeginString, FIXString("FIXT.1.1")).
		SetField(tagBodyLength, FIXString("0")).
		SetField(tagSenderCompID, FIXString("0")).
		SetField(tagTargetCompID, FIXString("0")).
		SetField(tagMsgSeqNum, FIXString("0"))

	// Sending time is required. invalidMsg2.Header.FieldMap.SetField(tag.SendingTime, "0")).
	tag := tagSendingTime
	msgBytes := invalidMsg2.build()

	return validateTest{
		TestName:             "FieldNotFoundHeader FIXT",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonRequiredTagMissing,
		ExpectedRefTagID:     &tag,
	}
}

func tcTagSpecifiedWithoutAValue() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	validator := NewValidator(defaultValidatorSettings, dict, nil)
	builder := createFIX40NewOrderSingle()

	bogusTag := Tag(109)
	builder.Body.SetField(bogusTag, FIXString(""))
	msgBytes := builder.build()

	return validateTest{
		TestName:             "Tag SpecifiedWithoutAValue",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonTagSpecifiedWithoutAValue,
		ExpectedRefTagID:     &bogusTag,
	}
}

func tcTagSpecifiedWithoutAValueValidateHasValues() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	settings := defaultValidatorSettings
	settings.RejectInvalidMessage = false
	settings.CheckFieldsHaveValues = true
	validator := NewValidator(settings, dict, nil)
	builder := createFIX40NewOrderSingle()

	bogusTag := Tag(109)
	builder.Body.SetField(bogusTag, FIXString(""))
	msgBytes := builder.build()

	return validateTest{
		TestName:             "Tag SpecifiedWithoutAValue",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonTagSpecifiedWithoutAValue,
		ExpectedRefTagID:     &bogusTag,
	}
}

func tcTagSpecifiedWithoutAValueValidateHasValuesNoDataDictionary() validateTest {
	settings := defaultValidatorSettings
	settings.RejectInvalidMessage = false
	settings.CheckFieldsHaveValues = true
	validator := NewValidator(settings, nil, nil)
	builder := createFIX40NewOrderSingle()

	bogusTag := Tag(109)
	builder.Body.SetField(bogusTag, FIXString(""))
	msgBytes := builder.build()

	return validateTest{
		TestName:             "Tag SpecifiedWithoutAValue",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonTagSpecifiedWithoutAValue,
		ExpectedRefTagID:     &bogusTag,
	}
}

func tcTagSpecifiedWithoutAValueFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	validator := NewValidator(defaultValidatorSettings, appDict, tDict)
	builder := createFIX50SP2NewOrderSingle()

	bogusTag := Tag(109)
	builder.Body.SetField(bogusTag, FIXString(""))
	msgBytes := builder.build()

	return validateTest{
		TestName:             "Tag SpecifiedWithoutAValue FIXT",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonTagSpecifiedWithoutAValue,
		ExpectedRefTagID:     &bogusTag,
	}
}

func tcTagSpecifiedWithoutAValueFixTValidateHasValues() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	validator := NewValidator(defaultValidatorSettings, appDict, tDict)
	builder := createFIX50SP2NewOrderSingle()

	bogusTag := Tag(109)
	builder.Body.SetField(bogusTag, FIXString(""))
	msgBytes := builder.build()

	return validateTest{
		TestName:             "Tag SpecifiedWithoutAValue FIXT",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonTagSpecifiedWithoutAValue,
		ExpectedRefTagID:     &bogusTag,
	}
}

func tcInvalidMsgType() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	validator := NewValidator(defaultValidatorSettings, dict, nil)
	builder := createFIX40NewOrderSingle()
	builder.Header.SetField(tagMsgType, FIXString("z"))
	msgBytes := builder.build()

	return validateTest{
		TestName:             "Invalid MsgType",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonInvalidMsgType,
	}
}

func tcInvalidMsgTypeFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	validator := NewValidator(defaultValidatorSettings, appDict, tDict)
	builder := createFIX50SP2NewOrderSingle()
	builder.Header.SetField(tagMsgType, FIXString("zz"))
	msgBytes := builder.build()

	return validateTest{
		TestName:             "Invalid MsgType FIXT",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonInvalidMsgType,
	}
}

func tcValueIsIncorrect() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	validator := NewValidator(defaultValidatorSettings, dict, nil)

	tag := Tag(21)
	builder := createFIX40NewOrderSingle()
	builder.Body.SetField(tag, FIXString("4"))
	msgBytes := builder.build()

	return validateTest{
		TestName:             "ValueIsIncorrect",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonValueIsIncorrect,
		ExpectedRefTagID:     &tag,
	}
}

func tcValueIsIncorrectFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	validator := NewValidator(defaultValidatorSettings, appDict, tDict)

	tag := Tag(21)
	builder := createFIX50SP2NewOrderSingle()
	builder.Body.SetField(tag, FIXString("4"))
	msgBytes := builder.build()

	return validateTest{
		TestName:             "ValueIsIncorrect FIXT",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonValueIsIncorrect,
		ExpectedRefTagID:     &tag,
	}
}

func tcIncorrectDataFormatForValue() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	validator := NewValidator(defaultValidatorSettings, dict, nil)
	builder := createFIX40NewOrderSingle()
	tag := Tag(38)
	builder.Body.SetField(tag, FIXString("+200.00"))
	msgBytes := builder.build()

	return validateTest{
		TestName:             "IncorrectDataFormatForValue",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonIncorrectDataFormatForValue,
		ExpectedRefTagID:     &tag,
	}
}

func tcIncorrectDataFormatForValueFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	validator := NewValidator(defaultValidatorSettings, appDict, tDict)
	builder := createFIX50SP2NewOrderSingle()
	tag := Tag(38)
	builder.Body.SetField(tag, FIXString("+200.00"))
	msgBytes := builder.build()

	return validateTest{
		TestName:             "IncorrectDataFormatForValue FIXT",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonIncorrectDataFormatForValue,
		ExpectedRefTagID:     &tag,
	}
}

func tcTagSpecifiedOutOfRequiredOrderHeader() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	validator := NewValidator(defaultValidatorSettings, dict, nil)

	builder := createFIX40NewOrderSingle()
	tag := tagOnBehalfOfCompID
	// Should be in header.
	builder.Body.SetField(tag, FIXString("CWB"))
	msgBytes := builder.build()

	return validateTest{
		TestName:             "Tag specified out of required order in Header",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonTagSpecifiedOutOfRequiredOrder,
		ExpectedRefTagID:     &tag,
	}
}

func tcTagSpecifiedOutOfRequiredOrderHeaderFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	validator := NewValidator(defaultValidatorSettings, appDict, tDict)

	builder := createFIX50SP2NewOrderSingle()
	tag := tagOnBehalfOfCompID
	// Should be in header.
	builder.Body.SetField(tag, FIXString("CWB"))
	msgBytes := builder.build()

	return validateTest{
		TestName:             "Tag specified out of required order in Header FIXT",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonTagSpecifiedOutOfRequiredOrder,
		ExpectedRefTagID:     &tag,
	}
}

func tcTagSpecifiedOutOfRequiredOrderTrailer() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	validator := NewValidator(defaultValidatorSettings, dict, nil)

	builder := createFIX40NewOrderSingle()
	tag := tagSignature
	// Should be in trailer.
	builder.Body.SetField(tag, FIXString("SIG"))
	msgBytes := builder.build()

	refTag := Tag(100)
	return validateTest{
		TestName:             "Tag specified out of required order in Trailer",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonTagSpecifiedOutOfRequiredOrder,
		ExpectedRefTagID:     &refTag,
	}
}

func tcTagSpecifiedOutOfRequiredOrderTrailerFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	validator := NewValidator(defaultValidatorSettings, appDict, tDict)

	builder := createFIX50SP2NewOrderSingle()
	tag := tagSignature
	// Should be in trailer.
	builder.Body.SetField(tag, FIXString("SIG"))
	msgBytes := builder.build()

	refTag := Tag(100)
	return validateTest{
		TestName:             "Tag specified out of required order in Trailer FIXT",
		Validator:            validator,
		MessageBytes:         msgBytes,
		ExpectedRejectReason: rejectReasonTagSpecifiedOutOfRequiredOrder,
		ExpectedRefTagID:     &refTag,
	}
}

func tcInvalidTagCheckDisabled() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	customValidatorSettings := defaultValidatorSettings
	customValidatorSettings.RejectInvalidMessage = false
	validator := NewValidator(customValidatorSettings, dict, nil)

	builder := createFIX40NewOrderSingle()
	tag := Tag(9999)
	builder.Body.SetField(tag, FIXString("hello"))
	msgBytes := builder.build()

	return validateTest{
		TestName:          "Invalid Tag Check - Disabled",
		Validator:         validator,
		MessageBytes:      msgBytes,
		DoNotExpectReject: true,
	}
}

func tcInvalidTagCheckDisabledFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	customValidatorSettings := defaultValidatorSettings
	customValidatorSettings.RejectInvalidMessage = false
	validator := NewValidator(customValidatorSettings, appDict, tDict)

	builder := createFIX50SP2NewOrderSingle()
	tag := Tag(9999)
	builder.Body.SetField(tag, FIXString("hello"))
	msgBytes := builder.build()

	return validateTest{
		TestName:          "Invalid Tag Check - Disabled FIXT",
		Validator:         validator,
		MessageBytes:      msgBytes,
		DoNotExpectReject: true,
	}
}

func tcInvalidTagCheckEnabled() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	customValidatorSettings := defaultValidatorSettings
	customValidatorSettings.RejectInvalidMessage = true
	validator := NewValidator(customValidatorSettings, dict, nil)

	builder := createFIX40NewOrderSingle()
	tag := Tag(9999)
	builder.Body.SetField(tag, FIXString("hello"))
	msgBytes := builder.build()

	return validateTest{
		TestName:          "Invalid Tag Check - Enabled",
		Validator:         validator,
		MessageBytes:      msgBytes,
		DoNotExpectReject: false,
		ExpectedRefTagID:  &tag,
	}
}

func tcInvalidTagCheckEnabledFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	customValidatorSettings := defaultValidatorSettings
	customValidatorSettings.RejectInvalidMessage = true
	validator := NewValidator(customValidatorSettings, appDict, tDict)

	builder := createFIX50SP2NewOrderSingle()
	tag := Tag(9999)
	builder.Body.SetField(tag, FIXString("hello"))
	msgBytes := builder.build()

	return validateTest{
		TestName:          "Invalid Tag Check - Enabled FIXT",
		Validator:         validator,
		MessageBytes:      msgBytes,
		DoNotExpectReject: false,
		ExpectedRefTagID:  &tag,
	}
}

func tcAllowUnknownMessageFieldsEnabled() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	customValidatorSettings := defaultValidatorSettings
	customValidatorSettings.AllowUnknownMessageFields = true
	validator := NewValidator(customValidatorSettings, dict, nil)

	builder := createFIX40NewOrderSingle()
	tag := Tag(41)
	builder.Body.SetField(tag, FIXString("hello"))
	msgBytes := builder.build()

	return validateTest{
		TestName:          "Allow Unknown Message Fields - Enabled",
		Validator:         validator,
		MessageBytes:      msgBytes,
		DoNotExpectReject: true,
	}
}

func tcAllowUnknownMessageFieldsEnabledFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	customValidatorSettings := defaultValidatorSettings
	customValidatorSettings.AllowUnknownMessageFields = true
	validator := NewValidator(customValidatorSettings, appDict, tDict)

	builder := createFIX50SP2NewOrderSingle()
	tag := Tag(41)
	builder.Body.SetField(tag, FIXString("hello"))
	msgBytes := builder.build()

	return validateTest{
		TestName:          "Allow Unknown Message Fields - Enabled FIXT",
		Validator:         validator,
		MessageBytes:      msgBytes,
		DoNotExpectReject: true,
	}
}

func tcAllowUnknownMessageFieldsDisabled() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	customValidatorSettings := defaultValidatorSettings
	customValidatorSettings.AllowUnknownMessageFields = false
	validator := NewValidator(customValidatorSettings, dict, nil)

	builder := createFIX40NewOrderSingle()
	tag := Tag(41)
	builder.Body.SetField(tag, FIXString("hello"))
	msgBytes := builder.build()

	return validateTest{
		TestName:             "Allow Unknown Message Fields - Disabled",
		Validator:            validator,
		MessageBytes:         msgBytes,
		DoNotExpectReject:    false,
		ExpectedRejectReason: rejectReasonTagNotDefinedForThisMessageType,
		ExpectedRefTagID:     &tag,
	}
}

func tcAllowUnknownMessageFieldsDisabledFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	customValidatorSettings := defaultValidatorSettings
	customValidatorSettings.RejectInvalidMessage = true
	validator := NewValidator(customValidatorSettings, appDict, tDict)

	builder := createFIX50SP2NewOrderSingle()
	tag := Tag(41)
	builder.Body.SetField(tag, FIXString("hello"))
	msgBytes := builder.build()

	return validateTest{
		TestName:             "Allow Unknown Message Fields - Disabled FIXT",
		Validator:            validator,
		MessageBytes:         msgBytes,
		DoNotExpectReject:    false,
		ExpectedRejectReason: rejectReasonTagNotDefinedForThisMessageType,
		ExpectedRefTagID:     &tag,
	}
}

func tcCheckUserDefinedFieldsEnabled() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	customValidatorSettings := defaultValidatorSettings
	customValidatorSettings.CheckUserDefinedFields = true
	validator := NewValidator(customValidatorSettings, dict, nil)

	builder := createFIX40NewOrderSingle()
	tag := Tag(9999)
	builder.Body.SetField(tag, FIXString("hello"))
	msgBytes := builder.build()

	return validateTest{
		TestName:          "Check User Defined Fields - Enabled",
		Validator:         validator,
		MessageBytes:      msgBytes,
		DoNotExpectReject: false,
		ExpectedRefTagID:  &tag,
	}
}

func tcCheckUserDefinedFieldsEnabledFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	customValidatorSettings := defaultValidatorSettings
	customValidatorSettings.RejectInvalidMessage = true
	validator := NewValidator(customValidatorSettings, appDict, tDict)

	builder := createFIX50SP2NewOrderSingle()
	tag := Tag(9999)
	builder.Body.SetField(tag, FIXString("hello"))
	msgBytes := builder.build()

	return validateTest{
		TestName:          "Check User Defined Fields - Enabled FIXT",
		Validator:         validator,
		MessageBytes:      msgBytes,
		DoNotExpectReject: false,
		ExpectedRefTagID:  &tag,
	}
}

func tcCheckUserDefinedFieldsDisabled() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	customValidatorSettings := defaultValidatorSettings
	customValidatorSettings.CheckUserDefinedFields = false
	validator := NewValidator(customValidatorSettings, dict, nil)

	builder := createFIX40NewOrderSingle()
	tag := Tag(9999)
	builder.Body.SetField(tag, FIXString("hello"))
	msgBytes := builder.build()

	return validateTest{
		TestName:          "Check User Defined Fields - Disabled",
		Validator:         validator,
		MessageBytes:      msgBytes,
		DoNotExpectReject: true,
	}
}

func tcCheckUserDefinedFieldsDisabledFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	customValidatorSettings := defaultValidatorSettings
	customValidatorSettings.CheckUserDefinedFields = false
	validator := NewValidator(customValidatorSettings, appDict, tDict)

	builder := createFIX50SP2NewOrderSingle()
	tag := Tag(9999)
	builder.Body.SetField(tag, FIXString("hello"))
	msgBytes := builder.build()

	return validateTest{
		TestName:          "Check User Defined Fields - Disabled FIXT",
		Validator:         validator,
		MessageBytes:      msgBytes,
		DoNotExpectReject: true,
	}
}

func tcTagSpecifiedOutOfRequiredOrderDisabledHeader() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	customValidatorSettings := defaultValidatorSettings
	customValidatorSettings.CheckFieldsOutOfOrder = false
	validator := NewValidator(customValidatorSettings, dict, nil)

	builder := createFIX40NewOrderSingle()
	tag := tagOnBehalfOfCompID
	// Should be in header.
	builder.Body.SetField(tag, FIXString("CWB"))
	msgBytes := builder.build()

	return validateTest{
		TestName:          "Tag specified out of required order in Header - Disabled",
		Validator:         validator,
		MessageBytes:      msgBytes,
		DoNotExpectReject: true,
	}
}

func tcTagSpecifiedOutOfRequiredOrderDisabledHeaderFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	customValidatorSettings := defaultValidatorSettings
	customValidatorSettings.CheckFieldsOutOfOrder = false
	validator := NewValidator(customValidatorSettings, appDict, tDict)

	builder := createFIX50SP2NewOrderSingle()
	tag := tagOnBehalfOfCompID
	// Should be in header.
	builder.Body.SetField(tag, FIXString("CWB"))
	msgBytes := builder.build()

	return validateTest{
		TestName:          "Tag specified out of required order in Header - Disabled FIXT",
		Validator:         validator,
		MessageBytes:      msgBytes,
		DoNotExpectReject: true,
	}
}

func tcTagSpecifiedOutOfRequiredOrderDisabledTrailer() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	customValidatorSettings := defaultValidatorSettings
	customValidatorSettings.CheckFieldsOutOfOrder = false
	validator := NewValidator(customValidatorSettings, dict, nil)

	builder := createFIX40NewOrderSingle()
	tag := tagSignature
	// Should be in trailer.
	builder.Body.SetField(tag, FIXString("SIG"))
	msgBytes := builder.build()

	return validateTest{
		TestName:          "Tag specified out of required order in Trailer - Disabled",
		Validator:         validator,
		MessageBytes:      msgBytes,
		DoNotExpectReject: true,
	}
}

func tcTagSpecifiedOutOfRequiredOrderDisabledTrailerFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	customValidatorSettings := defaultValidatorSettings
	customValidatorSettings.CheckFieldsOutOfOrder = false
	validator := NewValidator(customValidatorSettings, appDict, tDict)

	builder := createFIX50SP2NewOrderSingle()
	tag := tagSignature
	// Should be in trailer.
	builder.Body.SetField(tag, FIXString("SIG"))
	msgBytes := builder.build()

	return validateTest{
		TestName:          "Tag specified out of required order in Trailer - Disabled FIXT",
		Validator:         validator,
		MessageBytes:      msgBytes,
		DoNotExpectReject: true,
	}
}

func tcTagAppearsMoreThanOnce() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX40.xml")
	validator := NewValidator(defaultValidatorSettings, dict, nil)
	tag := Tag(40)

	return validateTest{
		TestName:             "Tag appears more than once",
		Validator:            validator,
		MessageBytes:         []byte("8=FIX.4.09=10735=D34=249=TW52=20060102-15:04:0556=ISLD11=ID21=140=140=254=138=20055=INTC60=20060102-15:04:0510=234"),
		ExpectedRejectReason: rejectReasonTagAppearsMoreThanOnce,
		ExpectedRefTagID:     &tag,
	}
}

func tcTagAppearsMoreThanOnceFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	validator := NewValidator(defaultValidatorSettings, appDict, tDict)
	tag := Tag(40)

	return validateTest{
		TestName:             "Tag appears more than once FIXT",
		Validator:            validator,
		MessageBytes:         []byte("8=FIXT.1.19=10735=D34=249=TW52=20060102-15:04:0556=ISLD11=ID21=140=140=254=138=20055=INTC60=20060102-15:04:0510=234"),
		ExpectedRejectReason: rejectReasonTagAppearsMoreThanOnce,
		ExpectedRefTagID:     &tag,
	}
}

func tcFloatValidation() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX42.xml")
	validator := NewValidator(defaultValidatorSettings, dict, nil)
	tag := Tag(38)
	return validateTest{
		TestName:             "FloatValidation",
		Validator:            validator,
		MessageBytes:         []byte("8=FIX.4.29=10635=D34=249=TW52=20140329-22:38:4556=ISLD11=ID21=140=154=138=+200.0055=INTC60=20140329-22:38:4510=178"),
		ExpectedRejectReason: rejectReasonIncorrectDataFormatForValue,
		ExpectedRefTagID:     &tag,
	}
}

func tcFloatValidationFixT() validateTest {
	tDict, _ := datadictionary.Parse("spec/FIXT11.xml")
	appDict, _ := datadictionary.Parse("spec/FIX50SP2.xml")
	validator := NewValidator(defaultValidatorSettings, appDict, tDict)
	tag := Tag(38)
	return validateTest{
		TestName:             "FloatValidation FIXT",
		Validator:            validator,
		MessageBytes:         []byte("8=FIXT.1.19=10635=D34=249=TW52=20140329-22:38:4556=ISLD11=ID21=140=154=138=+200.0055=INTC60=20140329-22:38:4510=178"),
		ExpectedRejectReason: rejectReasonIncorrectDataFormatForValue,
		ExpectedRefTagID:     &tag,
	}
}

func tcMultipleRepeatingGroupFields() validateTest {
	dict, _ := datadictionary.Parse("spec/FIX43.xml")
	validator := NewValidator(defaultValidatorSettings, dict, nil)
	return validateTest{
		TestName:          "Multiple repeating group fields in a message",
		Validator:         validator,
		MessageBytes:      []byte("8=FIX.4.39=17635=D34=249=TW52=20140329-22:38:4556=ISLD11=ID453=2448=PARTYID452=3523=SUBID448=PARTYID2452=378=179=ACCOUNT80=121=140=154=138=20055=INTC60=20140329-22:38:4510=178"),
		DoNotExpectReject: true,
	}
}

func TestValidateVisitField(t *testing.T) {
	fieldType0 := datadictionary.NewFieldType("myfield", 11, "STRING")
	fieldDef0 := &datadictionary.FieldDef{FieldType: fieldType0}

	fieldType1 := datadictionary.NewFieldType("myfield", 2, "STRING")
	fieldDef1 := &datadictionary.FieldDef{FieldType: fieldType1, Fields: []*datadictionary.FieldDef{}}

	fieldType2 := datadictionary.NewFieldType("myfield", 3, "STRING")
	fieldDef2 := &datadictionary.FieldDef{FieldType: fieldType2, Fields: []*datadictionary.FieldDef{}}

	groupFieldType := datadictionary.NewFieldType("mygroupfield", 1, "INT")
	groupFieldDef := &datadictionary.FieldDef{FieldType: groupFieldType, Fields: []*datadictionary.FieldDef{fieldDef1, fieldDef2}}

	var field TagValue
	field.init(Tag(11), []byte("value"))

	var repField1 TagValue
	var repField2 TagValue
	repField1.init(Tag(2), []byte("a"))
	repField2.init(Tag(3), []byte("a"))

	var groupID TagValue
	groupID.init(Tag(1), []byte("1"))

	var groupID2 TagValue
	groupID2.init(Tag(1), []byte("2"))

	var groupID3 TagValue
	groupID3.init(Tag(1), []byte("3"))

	var tests = []struct {
		fieldDef             *datadictionary.FieldDef
		fields               []TagValue
		expectedRemFields    int
		expectReject         bool
		expectedRejectReason int
	}{
		// Non-repeating.
		{expectedRemFields: 0,
			fieldDef: fieldDef0,
			fields:   []TagValue{field}},
		// Single field group.
		{expectedRemFields: 0,
			fieldDef: groupFieldDef,
			fields:   []TagValue{groupID, repField1}},
		// Multiple field group.
		{expectedRemFields: 0,
			fieldDef: groupFieldDef,
			fields:   []TagValue{groupID, repField1, repField2}},
		// Test with trailing tag not in group.
		{expectedRemFields: 1,
			fieldDef: groupFieldDef,
			fields:   []TagValue{groupID, repField1, repField2, field}},
		// Repeats.
		{expectedRemFields: 1,
			fieldDef: groupFieldDef,
			fields:   []TagValue{groupID2, repField1, repField2, repField1, repField2, field}},
		// REJECT: group size declared > actual group size.
		{expectReject: true,
			fieldDef:             groupFieldDef,
			fields:               []TagValue{groupID3, repField1, repField2, repField1, repField2, field},
			expectedRejectReason: rejectReasonIncorrectNumInGroupCountForRepeatingGroup,
		},
		{expectReject: true,
			fieldDef:             groupFieldDef,
			fields:               []TagValue{groupID3, repField1, repField1, field},
			expectedRejectReason: rejectReasonIncorrectNumInGroupCountForRepeatingGroup,
		},
		// REJECT: group size declared < actual group size.
		{expectReject: true,
			fieldDef:             groupFieldDef,
			fields:               []TagValue{groupID, repField1, repField2, repField1, repField2, field},
			expectedRejectReason: rejectReasonIncorrectNumInGroupCountForRepeatingGroup,
		},
	}

	for _, test := range tests {
		remFields, reject := validateVisitField(test.fieldDef, test.fields)

		if test.expectReject {
			if reject == nil {
				t.Error("Expected Reject")
			}

			if reject.RejectReason() != test.expectedRejectReason {
				t.Errorf("Expected reject reason %v got %v", test.expectedRejectReason, reject.RejectReason())
			}
			continue
		}

		if reject != nil {
			t.Errorf("Unexpected reject: %v", reject)
		}

		if len(remFields) != test.expectedRemFields {
			t.Errorf("Expected len %v got %v", test.expectedRemFields, len(remFields))
		}
	}
}
