From 47b309c4d64e94fdafd5928123099ae16fa8ae66 Mon Sep 17 00:00:00 2001
From: Jono Wenger <jono@elixxir.io>
Date: Wed, 17 Aug 2022 10:31:25 -0700
Subject: [PATCH] Implement ud.go

---
 main.go         |  95 +++++++------
 wasm/e2e.go     |  18 +--
 wasm/ud.go      | 365 ++++++++++++++++++++++++++++++++++++++++++++++++
 wasm/ud_test.go |  36 +++++
 4 files changed, 461 insertions(+), 53 deletions(-)
 create mode 100644 wasm/ud.go
 create mode 100644 wasm/ud_test.go

diff --git a/main.go b/main.go
index d53e5c47..ccbe089a 100644
--- a/main.go
+++ b/main.go
@@ -19,14 +19,38 @@ import (
 func main() {
 	fmt.Println("Go Web Assembly")
 
+	// wasm/backup.go
+	js.Global().Set("NewCmixFromBackup", js.FuncOf(wasm.NewCmixFromBackup))
+	js.Global().Set("InitializeBackup", js.FuncOf(wasm.InitializeBackup))
+	js.Global().Set("ResumeBackup", js.FuncOf(wasm.ResumeBackup))
+
+	// wasm/broadcast.go
+	js.Global().Set("NewBroadcastChannel", js.FuncOf(wasm.NewBroadcastChannel))
+
 	// wasm/cmix.go
 	js.Global().Set("NewCmix", js.FuncOf(wasm.NewCmix))
 	js.Global().Set("LoadCmix", js.FuncOf(wasm.LoadCmix))
 
+	// wasm/dummy.go
+	js.Global().Set("NewDummyTrafficManager",
+		js.FuncOf(wasm.NewDummyTrafficManager))
+
 	// wasm/e2e.go
 	js.Global().Set("Login", js.FuncOf(wasm.Login))
 	js.Global().Set("LoginEphemeral", js.FuncOf(wasm.LoginEphemeral))
 
+	// wasm/errors.go
+	js.Global().Set("CreateUserFriendlyErrorMessage",
+		js.FuncOf(wasm.CreateUserFriendlyErrorMessage))
+	js.Global().Set("UpdateCommonErrors",
+		js.FuncOf(wasm.UpdateCommonErrors))
+
+	// wasm/fileTransfer.go
+	js.Global().Set("InitFileTransfer", js.FuncOf(wasm.InitFileTransfer))
+
+	// wasm/group.go
+	js.Global().Set("NewGroupChat", js.FuncOf(wasm.NewGroupChat))
+
 	// wasm/identity.go
 	js.Global().Set("StoreReceptionIdentity",
 		js.FuncOf(wasm.StoreReceptionIdentity))
@@ -41,6 +65,15 @@ func main() {
 	js.Global().Set("GetFactsFromContact",
 		js.FuncOf(wasm.GetFactsFromContact))
 
+	// wasm/logging.go
+	js.Global().Set("LogLevel", js.FuncOf(wasm.LogLevel))
+	js.Global().Set("RegisterLogWriter", js.FuncOf(wasm.RegisterLogWriter))
+	js.Global().Set("EnableGrpcLogs", js.FuncOf(wasm.EnableGrpcLogs))
+
+	// wasm/ndf.go
+	js.Global().Set("DownloadAndVerifySignedNdfWithUrl",
+		js.FuncOf(wasm.DownloadAndVerifySignedNdfWithUrl))
+
 	// wasm/params.go
 	js.Global().Set("GetDefaultCMixParams",
 		js.FuncOf(wasm.GetDefaultCMixParams))
@@ -53,61 +86,35 @@ func main() {
 	js.Global().Set("GetDefaultE2eFileTransferParams",
 		js.FuncOf(wasm.GetDefaultE2eFileTransferParams))
 
-	// wasm/logging.go
-	js.Global().Set("LogLevel", js.FuncOf(wasm.LogLevel))
-	js.Global().Set("RegisterLogWriter", js.FuncOf(wasm.RegisterLogWriter))
-	js.Global().Set("EnableGrpcLogs", js.FuncOf(wasm.EnableGrpcLogs))
-
-	// wasm/ndf.go
-	js.Global().Set("DownloadAndVerifySignedNdfWithUrl",
-		js.FuncOf(wasm.DownloadAndVerifySignedNdfWithUrl))
-
-	// wasm/version.go
-	js.Global().Set("GetVersion", js.FuncOf(wasm.GetVersion))
-	js.Global().Set("GetGitVersion", js.FuncOf(wasm.GetGitVersion))
-	js.Global().Set("GetDependencies", js.FuncOf(wasm.GetDependencies))
-
-	// wasm/secrets.go
-	js.Global().Set("GenerateSecret", js.FuncOf(wasm.GenerateSecret))
-
-	// wasm/dummy.go
-	js.Global().Set("NewDummyTrafficManager",
-		js.FuncOf(wasm.NewDummyTrafficManager))
-
-	// bindings/broadcast.go
-	js.Global().Set("NewBroadcastChannel", js.FuncOf(wasm.NewBroadcastChannel))
-
-	// bindings/backup.go
-	js.Global().Set("NewCmixFromBackup", js.FuncOf(wasm.NewCmixFromBackup))
-	js.Global().Set("InitializeBackup", js.FuncOf(wasm.InitializeBackup))
-	js.Global().Set("ResumeBackup", js.FuncOf(wasm.ResumeBackup))
-
-	// bindings/errors.go
-	js.Global().Set("CreateUserFriendlyErrorMessage",
-		js.FuncOf(wasm.CreateUserFriendlyErrorMessage))
-	js.Global().Set("UpdateCommonErrors",
-		js.FuncOf(wasm.UpdateCommonErrors))
-
-	// bindings/fileTransfer.go
-	js.Global().Set("InitFileTransfer", js.FuncOf(wasm.InitFileTransfer))
-
-	// bindings/group.go
-	js.Global().Set("NewGroupChat", js.FuncOf(wasm.NewGroupChat))
-
-	// bindings/restlike.go
+	// wasm/restlike.go
 	js.Global().Set("RestlikeRequest", js.FuncOf(wasm.RestlikeRequest))
 	js.Global().Set("RestlikeRequestAuth", js.FuncOf(wasm.RestlikeRequestAuth))
 
-	// bindings/restlikeSingle.go
+	// wasm/restlikeSingle.go
 	js.Global().Set("RequestRestLike",
 		js.FuncOf(wasm.RequestRestLike))
 	js.Global().Set("AsyncRequestRestLike",
 		js.FuncOf(wasm.AsyncRequestRestLike))
 
-	// bindings/single.go
+	// wasm/secrets.go
+	js.Global().Set("GenerateSecret", js.FuncOf(wasm.GenerateSecret))
+
+	// wasm/single.go
 	js.Global().Set("TransmitSingleUse", js.FuncOf(wasm.TransmitSingleUse))
 	js.Global().Set("Listen", js.FuncOf(wasm.Listen))
 
+	// wasm/ud.go
+	js.Global().Set("NewOrLoadUd", js.FuncOf(wasm.NewOrLoadUd))
+	js.Global().Set("NewUdManagerFromBackup",
+		js.FuncOf(wasm.NewUdManagerFromBackup))
+	js.Global().Set("LookupUD", js.FuncOf(wasm.LookupUD))
+	js.Global().Set("SearchUD", js.FuncOf(wasm.SearchUD))
+
+	// wasm/version.go
+	js.Global().Set("GetVersion", js.FuncOf(wasm.GetVersion))
+	js.Global().Set("GetGitVersion", js.FuncOf(wasm.GetGitVersion))
+	js.Global().Set("GetDependencies", js.FuncOf(wasm.GetDependencies))
+
 	<-make(chan bool)
 	os.Exit(0)
 }
diff --git a/wasm/e2e.go b/wasm/e2e.go
index de160033..b0f79041 100644
--- a/wasm/e2e.go
+++ b/wasm/e2e.go
@@ -66,10 +66,10 @@ func newE2eJS(api *bindings.E2e) map[string]interface{} {
 	return e2eMap
 }
 
-// GetID returns the ID for this [bindings.E2e] in the e2eTracker.
+// GetID returns the ID for this E2e in the E2e tracker.
 //
 // Returns:
-//  - int of the ID
+//  - int
 func (e *E2e) GetID(js.Value, []js.Value) interface{} {
 	return e.api.GetID()
 }
@@ -110,15 +110,15 @@ func Login(_ js.Value, args []js.Value) interface{} {
 // in here. If callbacks is left nil, a default [auth.Callbacks] will be used.
 //
 // Parameters:
-//  - args[0] - ID of Cmix object in tracker (int)
-//  - args[1] - Javascript object that has functions that implement the
-//    [bindings.AuthCallbacks] interface
-//  - args[2] - JSON of the [xxdk.ReceptionIdentity] object (Uint8Array)
-//  - args[3] - JSON of [xxdk.E2EParams] (Uint8Array)
+//  - args[0] - ID of Cmix object in tracker (int).
+//  - args[1] - Javascript object that has functions that implement the.
+//    [bindings.AuthCallbacks] interface.
+//  - args[2] - JSON of the [xxdk.ReceptionIdentity] object (Uint8Array).
+//  - args[3] - JSON of [xxdk.E2EParams] (Uint8Array).
 //
 // Returns:
-//  - Javascript representation of the E2e object
-//  - Throws a TypeError if logging in fails
+//  - Javascript representation of the E2e object.
+//  - Throws a TypeError if logging in fails.
 func LoginEphemeral(_ js.Value, args []js.Value) interface{} {
 	callbacks := newAuthCallbacks(args[1])
 	identity := CopyBytesToGo(args[2])
diff --git a/wasm/ud.go b/wasm/ud.go
new file mode 100644
index 00000000..503781e9
--- /dev/null
+++ b/wasm/ud.go
@@ -0,0 +1,365 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+//go:build js && wasm
+
+package wasm
+
+import (
+	"gitlab.com/elixxir/client/bindings"
+	"syscall/js"
+)
+
+////////////////////////////////////////////////////////////////////////////////
+// Structs and Interfaces                                                     //
+////////////////////////////////////////////////////////////////////////////////
+
+// UserDiscovery wraps the [bindings.UserDiscovery] object so its methods can be
+// wrapped to be Javascript compatible.
+type UserDiscovery struct {
+	api *bindings.UserDiscovery
+}
+
+// newE2eJS creates a new Javascript compatible object (map[string]interface{})
+// that matches the E2e structure.
+func newUserDiscoveryJS(api *bindings.UserDiscovery) map[string]interface{} {
+	ud := UserDiscovery{api}
+	udMap := map[string]interface{}{
+		"GetID":                  js.FuncOf(ud.GetID),
+		"GetFacts":               js.FuncOf(ud.GetFacts),
+		"GetContact":             js.FuncOf(ud.GetContact),
+		"ConfirmFact":            js.FuncOf(ud.ConfirmFact),
+		"SendRegisterFact":       js.FuncOf(ud.SendRegisterFact),
+		"PermanentDeleteAccount": js.FuncOf(ud.PermanentDeleteAccount),
+		"RemoveFact":             js.FuncOf(ud.RemoveFact),
+	}
+
+	return udMap
+}
+
+// GetID returns the ID for this UserDiscovery in the UserDiscovery tracker.
+//
+// Returns:
+//  - int
+func (ud *UserDiscovery) GetID(js.Value, []js.Value) interface{} {
+	return ud.api.GetID()
+}
+
+// udNetworkStatus wraps Javascript callbacks to adhere to the
+// [bindings.UdNetworkStatus] interface.
+type udNetworkStatus struct {
+	udNetworkStatus func(args ...interface{}) js.Value
+}
+
+func (uns *udNetworkStatus) UdNetworkStatus() int {
+	return uns.udNetworkStatus().Int()
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Manager functions                                                          //
+////////////////////////////////////////////////////////////////////////////////
+
+// NewOrLoadUd loads an existing Manager from storage or creates a new one if
+// there is no extant storage information. Parameters need be provided to
+// specify how to connect to the User Discovery service. These parameters may be
+// used to contact either the UD server hosted by the xx network team or a
+// custom third-party operated server. For the former, all the information may
+// be pulled from the NDF using the bindings.
+//
+// Params
+//  - args[0] - ID of E2e object in tracker (int).
+//  - args[1] - Javascript object that has functions that implement the
+//    [bindings.UdNetworkStatus] interface. This is the network follower
+//    function wrapped in [bindings.UdNetworkStatus].
+//  - args[2] - the username the user wants to register with UD. If the user is
+//    already registered, this field may be blank (string).
+//  - args[3] - the registration validation signature; a signature provided by
+//    the network (i.e., the client registrar). This may be nil; however, UD may
+//    return an error in some cases (e.g., in a production level environment)
+//    (Uint8Array).
+//  - args[4] - the TLS certificate for the UD server this call will connect
+//    with. You may use the UD server run by the xx network team by using
+//    E2e.GetUdCertFromNdf (Uint8Array).
+//  - args[5] - marshalled [contact.Contact]. This represents the contact file
+//    of the server this call will connect with. You may use the UD server run
+//    by the xx network team by using E2e.GetUdContactFromNdf (Uint8Array).
+//  - args[6] - the IP address of the UD server this call will connect with. You
+//    may use the UD server run by the xx network team by using
+//    E2e.GetUdAddressFromNdf (string).
+//
+// Returns:
+//  - Javascript representation of the UserDiscovery object that is registered
+//    to the specified UD service.
+//  - Throws a TypeError if creating or loading fails.
+func NewOrLoadUd(_ js.Value, args []js.Value) interface{} {
+	e2eID := args[0].Int()
+	follower := &udNetworkStatus{args[1].Get("UdNetworkStatus").Invoke}
+	username := args[2].String()
+	registrationValidationSignature := CopyBytesToGo(args[3])
+	cert := CopyBytesToGo(args[4])
+	contactFile := CopyBytesToGo(args[5])
+	address := args[6].String()
+
+	api, err := bindings.NewOrLoadUd(e2eID, follower, username,
+		registrationValidationSignature, cert, contactFile, address)
+	if err != nil {
+		Throw(TypeError, err.Error())
+		return nil
+	}
+
+	return newUserDiscoveryJS(api)
+}
+
+// NewUdManagerFromBackup builds a new user discover manager from a backup. It
+// will construct a manager that is already registered and restore already
+// registered facts into store.
+//
+// Note that it can take in both ane email address and a phone number or both.
+// However, at least one fact must be specified; providing no facts will return
+// an error.
+//
+// Parameters:
+//  - args[0] - ID of E2e object in tracker (int).
+//  - args[1] - Javascript object that has functions that implement the
+//    [bindings.UdNetworkStatus] interface. This is the network follower
+//    function wrapped in [bindings.UdNetworkStatus].
+//  - args[2] - JSON of [fact.Fact] email address that is registered with UD
+//    (Uint8Array).
+//  - args[3] - JSON of [fact.Fact] phone number that is registered with UD
+//    (Uint8Array).
+//  - args[4] - the TLS certificate for the UD server this call will connect
+//    with. You may use the UD server run by the xx network team by using
+//    E2e.GetUdCertFromNdf (Uint8Array).
+//  - args[5] - marshalled [contact.Contact]. This represents the contact file
+//    of the server this call will connect with. You may use the UD server run
+//    by the xx network team by using E2e.GetUdContactFromNdf (Uint8Array).
+//  - args[6] - the IP address of the UD server this call will connect with. You
+//    may use the UD server run by the xx network team by using
+//    E2e.GetUdAddressFromNdf (string).
+//
+// Returns:
+//  - Javascript representation of the UserDiscovery object that is loaded from
+//    backup.
+//  - Throws a TypeError if getting UD from backup fails.
+func NewUdManagerFromBackup(_ js.Value, args []js.Value) interface{} {
+	e2eID := args[0].Int()
+	follower := &udNetworkStatus{args[1].Get("UdNetworkStatus").Invoke}
+	emailFactJson := CopyBytesToGo(args[2])
+	phoneFactJson := CopyBytesToGo(args[3])
+	cert := CopyBytesToGo(args[4])
+	contactFile := CopyBytesToGo(args[5])
+	address := args[6].String()
+
+	api, err := bindings.NewUdManagerFromBackup(e2eID, follower, emailFactJson,
+		phoneFactJson, cert, contactFile, address)
+	if err != nil {
+		Throw(TypeError, err.Error())
+		return nil
+	}
+
+	return newUserDiscoveryJS(api)
+}
+
+// GetFacts returns a JSON marshalled list of [fact.Fact] objects that exist
+// within the Store's registeredFacts map.
+//
+// Returns:
+//  - JSON of [fact.FactList] (Uint8Array).
+func (ud *UserDiscovery) GetFacts(js.Value, []js.Value) interface{} {
+	return CopyBytesToJS(ud.api.GetFacts())
+}
+
+// GetContact returns the marshalled bytes of the [contact.Contact] for UD as
+// retrieved from the NDF.
+//
+// Returns:
+//  - JSON of [contact.Contact] (Uint8Array).
+//  - Throws TypeError if getting the contact fails.
+func (ud *UserDiscovery) GetContact(js.Value, []js.Value) interface{} {
+	c, err := ud.api.GetContact()
+	if err != nil {
+		Throw(TypeError, err.Error())
+		return nil
+	}
+
+	return CopyBytesToJS(c)
+}
+
+// ConfirmFact confirms a fact first registered via SendRegisterFact. The
+// confirmation ID comes from SendRegisterFact while the code will come over the
+// associated communications system.
+//
+// Parameters:
+//  - args[0] - confirmation ID (string).
+//  - args[1] - code (string).
+//
+// Returns:
+//  - Throws TypeError if confirming the fact fails.
+func (ud *UserDiscovery) ConfirmFact(_ js.Value, args []js.Value) interface{} {
+	err := ud.api.ConfirmFact(args[0].String(), args[1].String())
+	if err != nil {
+		Throw(TypeError, err.Error())
+		return nil
+	}
+
+	return nil
+}
+
+// SendRegisterFact adds a fact for the user to user discovery. Will only
+// succeed if the user is already registered and the system does not have the
+// fact currently registered for any user.
+//
+// This does not complete the fact registration process, it returns a
+// confirmation ID instead. Over the communications system the fact is
+// associated with, a code will be sent. This confirmation ID needs to be called
+// along with the code to finalize the fact.
+//
+// Parameters:
+//  - args[0] - JSON of [fact.Fact] (Uint8Array).
+//
+// Returns:
+//  - The confirmation ID (string).
+//  - Throws TypeError if sending the fact fails.
+func (ud *UserDiscovery) SendRegisterFact(_ js.Value, args []js.Value) interface{} {
+	confirmationID, err := ud.api.SendRegisterFact(CopyBytesToGo(args[0]))
+	if err != nil {
+		Throw(TypeError, err.Error())
+		return nil
+	}
+
+	return confirmationID
+}
+
+// PermanentDeleteAccount removes the username associated with this user from
+// the UD service. This will only take a username type fact, and the fact must
+// be associated with this user.
+//
+// Parameters:
+//  - args[0] - JSON of [fact.Fact] (Uint8Array).
+//
+// Returns:
+//  - Throws TypeError if deletion fails.
+func (ud *UserDiscovery) PermanentDeleteAccount(_ js.Value, args []js.Value) interface{} {
+	err := ud.api.PermanentDeleteAccount(CopyBytesToGo(args[0]))
+	if err != nil {
+		Throw(TypeError, err.Error())
+		return nil
+	}
+
+	return nil
+}
+
+// RemoveFact removes a previously confirmed fact. This will fail if the fact
+// passed in is not UD service does not associate this fact with this user.
+//
+// Parameters:
+//  - args[0] - JSON of [fact.Fact] (Uint8Array).
+//
+// Returns:
+//  - Throws TypeError if removing the fact fails.
+func (ud *UserDiscovery) RemoveFact(_ js.Value, args []js.Value) interface{} {
+	err := ud.api.RemoveFact(CopyBytesToGo(args[0]))
+	if err != nil {
+		Throw(TypeError, err.Error())
+		return nil
+	}
+
+	return nil
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// User Discovery Lookup                                                      //
+////////////////////////////////////////////////////////////////////////////////
+
+// udLookupCallback wraps Javascript callbacks to adhere to the
+// [bindings.UdLookupCallback] interface.
+type udLookupCallback struct {
+	callback func(args ...interface{}) js.Value
+}
+
+func (ulc *udLookupCallback) Callback(contactBytes []byte, err error) {
+	ulc.callback(CopyBytesToJS(contactBytes), err.Error())
+}
+
+// LookupUD returns the public key of the passed ID as known by the user
+// discovery system or returns by the timeout.
+//
+// Parameters:
+//  - args[0] - ID of E2e object in tracker (int).
+//  - args[1] - JSON of User Discovery's [contact.Contact] (Uint8Array).
+//  - args[2] - Javascript object that has functions that implement the
+//    [bindings.UdLookupCallback] interface.
+//  - args[3] - JSON of [id.ID] for the user to look up (Uint8Array).
+//  - args[4] - JSON of [single.RequestParams] (Uint8Array).
+//
+// Returns:
+//  - JSON of [bindings.SingleUseSendReport], which can be passed into
+//    Cmix.WaitForRoundResult to see if the send succeeded.
+//  - Throws a TypeError if the lookup fails.
+func LookupUD(_ js.Value, args []js.Value) interface{} {
+	e2eID := args[0].Int()
+	udContact := CopyBytesToGo(args[1])
+	cb := &udLookupCallback{args[2].Get("Callback").Invoke}
+	lookupId := CopyBytesToGo(args[3])
+	singleRequestParamsJSON := CopyBytesToGo(args[4])
+
+	report, err := bindings.LookupUD(
+		e2eID, udContact, cb, lookupId, singleRequestParamsJSON)
+	if err != nil {
+		Throw(TypeError, err.Error())
+		return nil
+	}
+
+	return CopyBytesToJS(report)
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// User Discovery Search                                                      //
+////////////////////////////////////////////////////////////////////////////////
+
+// udSearchCallback wraps Javascript callbacks to adhere to the
+// [bindings.UdSearchCallback] interface.
+type udSearchCallback struct {
+	callback func(args ...interface{}) js.Value
+}
+
+func (usc *udSearchCallback) Callback(contactListJSON []byte, err error) {
+	usc.callback(CopyBytesToJS(contactListJSON), err.Error())
+}
+
+// SearchUD searches user discovery for the passed Facts. The searchCallback
+// will return a list of contacts, each having the facts it hit against. This is
+// NOT intended to be used to search for multiple users at once; that can have a
+// privacy reduction. Instead, it is intended to be used to search for a user
+// where multiple pieces of information is known.
+//
+// Parameters:
+//  - args[0] - ID of E2e object in tracker (int).
+//  - args[1] - JSON of User Discovery's [contact.Contact] (Uint8Array).
+//  - args[2] - JSON of [fact.FactList] (Uint8Array).
+//  - args[4] - JSON of [single.RequestParams] (Uint8Array).
+//
+// Returns:
+//  - JSON of [bindings.SingleUseSendReport], which can be passed into
+//    Cmix.WaitForRoundResult to see if the send succeeded.
+//  - Throws a TypeError if the search fails.
+func SearchUD(_ js.Value, args []js.Value) interface{} {
+	e2eID := args[0].Int()
+	udContact := CopyBytesToGo(args[1])
+	cb := &udSearchCallback{args[2].Get("Callback").Invoke}
+	factListJSON := CopyBytesToGo(args[3])
+	singleRequestParamsJSON := CopyBytesToGo(args[4])
+
+	report, err := bindings.SearchUD(
+		e2eID, udContact, cb, factListJSON, singleRequestParamsJSON)
+	if err != nil {
+		Throw(TypeError, err.Error())
+		return nil
+	}
+
+	return CopyBytesToJS(report)
+}
diff --git a/wasm/ud_test.go b/wasm/ud_test.go
new file mode 100644
index 00000000..cfd3766d
--- /dev/null
+++ b/wasm/ud_test.go
@@ -0,0 +1,36 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+//go:build js && wasm
+
+package wasm
+
+import (
+	"gitlab.com/elixxir/client/bindings"
+	"reflect"
+	"testing"
+)
+
+// Tests that the map representing UserDiscovery returned by newUserDiscoveryJS
+// contains all of the methods on UserDiscovery.
+func Test_newUserDiscoveryJS(t *testing.T) {
+	udType := reflect.TypeOf(&UserDiscovery{})
+
+	ud := newUserDiscoveryJS(&bindings.UserDiscovery{})
+	if len(ud) != udType.NumMethod() {
+		t.Errorf("UserDiscovery JS object does not have all methods."+
+			"\nexpected: %d\nreceived: %d", udType.NumMethod(), len(ud))
+	}
+
+	for i := 0; i < udType.NumMethod(); i++ {
+		method := udType.Method(i)
+
+		if _, exists := ud[method.Name]; !exists {
+			t.Errorf("Method %s does not exist.", method.Name)
+		}
+	}
+}
-- 
GitLab