// Copyright (c) 2013-2015 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package database_test

import (
	"fmt"
	"testing"

	"github.com/btcsuite/btcd/database"
)

var (
	// ignoreDbTypes are types which should be ignored when running tests
	// that iterate all supported DB types.  This allows some tests to add
	// bogus drivers for testing purposes while still allowing other tests
	// to easily iterate all supported drivers.
	ignoreDbTypes = map[string]bool{"createopenfail": true}
)

// testNewestShaEmpty ensures that NewestSha returns the values expected by
// the interface contract.
func testNewestShaEmpty(t *testing.T, db database.Db) {
	sha, height, err := db.NewestSha()
	if err != nil {
		t.Errorf("NewestSha error %v", err)
	}
	if !sha.IsEqual(&zeroHash) {
		t.Errorf("NewestSha wrong hash got: %s, want %s", sha, &zeroHash)

	}
	if height != -1 {
		t.Errorf("NewestSha wrong height got: %d, want %d", height, -1)
	}
}

// TestEmptyDB tests that empty databases are handled properly.
func TestEmptyDB(t *testing.T) {
	for _, dbType := range database.SupportedDBs() {
		// Ensure NewestSha returns expected values for a newly created
		// db.
		db, teardown, err := createDB(dbType, "emptydb", false)
		if err != nil {
			t.Errorf("Failed to create test database %v", err)
			return
		}
		testNewestShaEmpty(t, db)

		// Ensure NewestSha still returns expected values for an empty
		// database after reopen.
		db.Close()
		db, err = openDB(dbType, "emptydb")
		if err != nil {
			t.Errorf("Failed to open test database %v", err)
			return
		}
		testNewestShaEmpty(t, db)
		db.Close()

		// Clean up the old db.
		teardown()
	}
}

// TestAddDuplicateDriver ensures that adding a duplicate driver does not
// overwrite an existing one.
func TestAddDuplicateDriver(t *testing.T) {
	supportedDBs := database.SupportedDBs()
	if len(supportedDBs) == 0 {
		t.Errorf("TestAddDuplicateDriver: No backends to test")
		return
	}
	dbType := supportedDBs[0]

	// bogusCreateDB is a function which acts as a bogus create and open
	// driver function and intentionally returns a failure that can be
	// detected if the interface allows a duplicate driver to overwrite an
	// existing one.
	bogusCreateDB := func(args ...interface{}) (database.Db, error) {
		return nil, fmt.Errorf("duplicate driver allowed for database "+
			"type [%v]", dbType)
	}

	// Create a driver that tries to replace an existing one.  Set its
	// create and open functions to a function that causes a test failure if
	// they are invoked.
	driver := database.DriverDB{
		DbType:   dbType,
		CreateDB: bogusCreateDB,
		OpenDB:   bogusCreateDB,
	}
	database.AddDBDriver(driver)

	// Ensure creating a database of the type that we tried to replace
	// doesn't fail (if it does, it indicates the driver was erroneously
	// replaced).
	_, teardown, err := createDB(dbType, "dupdrivertest", true)
	if err != nil {
		t.Errorf("TestAddDuplicateDriver: %v", err)
		return
	}
	teardown()
}

// TestCreateOpenFail ensures that errors which occur while opening or closing
// a database are handled properly.
func TestCreateOpenFail(t *testing.T) {
	// bogusCreateDB is a function which acts as a bogus create and open
	// driver function that intentionally returns a failure which can be
	// detected.
	dbType := "createopenfail"
	openError := fmt.Errorf("failed to create or open database for "+
		"database type [%v]", dbType)
	bogusCreateDB := func(args ...interface{}) (database.Db, error) {
		return nil, openError
	}

	// Create and add driver that intentionally fails when created or opened
	// to ensure errors on database open and create are handled properly.
	driver := database.DriverDB{
		DbType:   dbType,
		CreateDB: bogusCreateDB,
		OpenDB:   bogusCreateDB,
	}
	database.AddDBDriver(driver)

	// Ensure creating a database with the new type fails with the expected
	// error.
	_, err := database.CreateDB(dbType, "createfailtest")
	if err != openError {
		t.Errorf("TestCreateOpenFail: expected error not received - "+
			"got: %v, want %v", err, openError)
		return
	}

	// Ensure opening a database with the new type fails with the expected
	// error.
	_, err = database.OpenDB(dbType, "openfailtest")
	if err != openError {
		t.Errorf("TestCreateOpenFail: expected error not received - "+
			"got: %v, want %v", err, openError)
		return
	}
}

// TestCreateOpenUnsupported ensures that attempting to create or open an
// unsupported database type is handled properly.
func TestCreateOpenUnsupported(t *testing.T) {
	// Ensure creating a database with an unsupported type fails with the
	// expected error.
	dbType := "unsupported"
	_, err := database.CreateDB(dbType, "unsupportedcreatetest")
	if err != database.ErrDbUnknownType {
		t.Errorf("TestCreateOpenUnsupported: expected error not "+
			"received - got: %v, want %v", err, database.ErrDbUnknownType)
		return
	}

	// Ensure opening a database with the new type fails with the expected
	// error.
	_, err = database.OpenDB(dbType, "unsupportedopentest")
	if err != database.ErrDbUnknownType {
		t.Errorf("TestCreateOpenUnsupported: expected error not "+
			"received - got: %v, want %v", err, database.ErrDbUnknownType)
		return
	}
}

// TestInterface performs tests for the various interfaces of the database
// package which require state in the database for each supported database
// type (those loaded in common_test.go that is).
func TestInterface(t *testing.T) {
	for _, dbType := range database.SupportedDBs() {
		if _, exists := ignoreDbTypes[dbType]; !exists {
			testInterface(t, dbType)
		}
	}
}

// TestReorganization performs reorganization tests for each supported DB type
func TestReorganization(t *testing.T) {
	for _, dbType := range database.SupportedDBs() {
		if _, exists := ignoreDbTypes[dbType]; !exists {
			testReorganization(t, dbType)
		}
	}
}
