diff --git a/src/plugin/action/whois.sjs b/src/plugin/action/whois.sjs index 28e12f3..c8a34dd 100644 --- a/src/plugin/action/whois.sjs +++ b/src/plugin/action/whois.sjs @@ -24,7 +24,7 @@ module.exports = function (client, rawf, emitter) { // TODO(Havvy) Figure out what the `server` parameter does. // TODO(Havvy) Move the multiple case to `whoisAll` or return a Promise<[Result], Error>`? return function whois (nickname, server, opts) { - if (memoizeSupport && opts.memoizeOver) { + if (memoizeSupport && opts && opts.memoizeOver) { const nicknameMap = memoizeMap.get(opts.memoizeOver); if (nicknameMap) { @@ -59,7 +59,7 @@ module.exports = function (client, rawf, emitter) { throw new Error("Whois command takes either a string (a single nick) or an array (of string nicks)"); } - if (server || multiple) { + if (server || (opts && opts.multiple)) { if (server) { rawf("WHOIS %s %s", server, nickname); } else { @@ -210,7 +210,7 @@ module.exports = function (client, rawf, emitter) { emitter.emit("join", result); }); - if (memoizeSupport && opts.memoizeOver) { + if (memoizeSupport && (opts && opts.memoizeOver)) { if (memoizeMap.has(opts.memoizeOver)) { memoizeMap.get(opts.memoizeOver).set(nickname, whoisPromise); } else { @@ -219,5 +219,7 @@ module.exports = function (client, rawf, emitter) { memoizeMap.set(opts.memoizeOver, newNicknameMap); } } + + return whoisPromise; }; }; \ No newline at end of file diff --git a/src/plugin/user.sjs b/src/plugin/user.sjs index 421791b..b2db590 100644 --- a/src/plugin/user.sjs +++ b/src/plugin/user.sjs @@ -5,8 +5,9 @@ module.exports = { init: function (client, imports) { const isIdentifiedAs = function isIdentifiedAs (nickname, accountname, opts) { client.debug("PluginUser", format("isIdentifiedAs(%s, %s)", nickname, accountname)); + const memoizeOver = (opts && opts.memoizeOver) || false; - return client.whois(nickname, false, {memoizeOver: opts.memoizeOver}) + return client.whois(nickname, false, {memoizeOver: memoizeOver}) .then(function (result) { return result .map(function (whoisInfo) { diff --git a/src/test/plugin-action.sjs b/src/test/plugin-action.sjs index 1ccb772..23db41c 100644 --- a/src/test/plugin-action.sjs +++ b/src/test/plugin-action.sjs @@ -303,9 +303,107 @@ describe "IRC Output Socket:" { } } - describe "Whois" { + describe only "Whois" { + // Maybe this should be Nicky instead? + function emitWhoisNicknameResponse (messageHandler, nickname) { + // Default to nickname "nickname". + nickname = nickname || "nickname"; + + // WHOIS nickname + // :irc.server.net 311 testbot nickname username hostname.net * :Real name + // :irc.server.net 313 testbot nickname irc.server.net :A test server + // :irc.server.net 317 testbot nickname 57895 1465689648 :seconds idle, signon time + // :irc.server.net 318 testbot nickname :End of /WHOIS list. + messageHandler.emit("rpl_whoisuser", { + replyname: "RPL_WHOISUSER", + nickname: nickname, + username: "username", + hostname: "hostname.net", + realname: "Real name", + hostmask: { + nickname: nickname, + username: "username", + hostname: "hostname.net" + } + }); + + messageHandler.emit("rpl_whoisserver", { + nickname: nickname, + server: "irc.server.net", + serverInfo: "A test server" + }); + + messageHandler.emit("rpl_whoisidle", { + nickname: nickname, + seconds: 123, + since: 456 + }); + + messageHandler.emit("rpl_endofwhois", { + nickname: nickname + }); + } + describe "A single user" { - it skip "resolves to Ok(WhoisInfo) when succeeded" {} + it "resolves to Ok(WhoisInfo) when succeeded" { + const whoisPromise = out.whois("nickname") + .then(function (whoisResult) { + assert(whoisResult.isOk()); + + const whois = whoisResult.ok(); + + assert(whois.nickname === "nickname"); + assert(whois.username === "username"); + assert(whois.hostname === "hostname.net"); + assert(equal(whois.hostmask, { + nickname: "nickname", + username: "username", + hostname: "hostname.net" + })); + assert(whois.realname === "Real name"); + assert(!whois.identified); + assert(whois.identifiedas === undefined); + assert(whois.server === "irc.server.net"); + assert(whois.serverInfo === "A test server"); + assert(whois.idleSeconds === 123); + assert(whois.loginTimestamp === 456); + assert(!whois.secureConnection); + assert(!whois.isBot); + assert(!whois.isHelpop); + assert(!whois.isOper); + }); + + messageHandler.emit("rpl_whoisuser", { + replyname: "RPL_WHOISUSER", + nickname: "nickname", + username: "username", + hostname: "hostname.net", + realname: "Real name", + hostmask: { + nickname: "nickname", + username: "username", + hostname: "hostname.net" + } + }); + + messageHandler.emit("rpl_whoisserver", { + nickname: "nickname", + server: "irc.server.net", + serverInfo: "A test server" + }); + + messageHandler.emit("rpl_whoisidle", { + nickname: "nickname", + seconds: 123, + since: 456 + }); + + messageHandler.emit("rpl_endofwhois", { + nickname: "nickname" + }); + + return whoisPromise; + } describe "Identifying" { it skip "JoinInfo has `\"identified\": false` when user is not identified" {} @@ -316,6 +414,90 @@ describe "IRC Output Socket:" { it skip "resovles to Fail(Numeric401Message) if WHOIS non-existent nickname" {} } + describe "memoization" { + it "can avoid a whois if memoized over an object of the caller's choice" { + // In a real case, this would be a PRIVMSG about the user. + const memoizationKey = {}; + var whoisResult1; + + const whoisPromise = out.whois("nickname", false, {memoizeOver: memoizationKey}) + .then(function (whoisResult) { + whoisResult1 = whoisResult; + + return out.whois("nickname", false, {memoizeOver: memoizationKey}); + }) + .then(function (whoisResult) { + assert(whoisResult1 === whoisResult); + assert(socket.raw.calledOnce); + }); + + emitWhoisNicknameResponse(messageHandler); + + return whoisPromise; + } + + it "will only avoid the whois request if the memoizeOver object is the same" { + const memoizationKey1 = {}; + const memoizationKey2 = {}; + var whoisResult1; + + const whoisPromise = out.whois("nickname", false, {memoizeOver: memoizationKey1}) + .then(function (whoisResult) { + whoisResult1 = whoisResult; + const whoisPromise2 = out.whois("nickname", false, {memoizeOver: memoizationKey2}); + emitWhoisNicknameResponse(messageHandler); + return whoisPromise2; + }) + .then(function (whoisResult) { + assert(whoisResult1 !== whoisResult); + assert(socket.raw.calledTwice); + }); + + emitWhoisNicknameResponse(messageHandler); + return whoisPromise; + } + + it "will not memoize if there is no memoizeOver object" { + var whoisResult1; + + const whoisPromise = out.whois("nickname") + .then(function (whoisResult) { + whoisResult1 = whoisResult; + const whoisPromise2 = out.whois("nickname"); + emitWhoisNicknameResponse(messageHandler); + return whoisPromise2; + }) + .then(function (whoisResult) { + assert(whoisResult1 !== whoisResult); + assert(socket.raw.calledTwice); + }); + + emitWhoisNicknameResponse(messageHandler); + return whoisPromise; + } + + it "will not send whois results for different nicknames and same memoization object" { + const memoizationKey = {}; + var whoisResult1; + + const whoisPromise = out.whois("nickname", false, {memoizeOver: memoizationKey}) + .then(function (whoisResult) { + whoisResult1 = whoisResult; + + const whoisPromise = out.whois("nickname2", false, {memoizeOver: memoizationKey}); + emitWhoisNicknameResponse(messageHandler, "nickname2"); + }) + .then(function (whoisResult) { + assert(whoisResult1 !== whoisResult); + assert(socket.raw.calledTwice); + }); + + emitWhoisNicknameResponse(messageHandler); + + return whoisPromise; + } + } + describe "timeouts" {} }