Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

General Data Validation - Boundary IO locations should be doing data validation and marshalling (and the decommissioning of GenericIdTypes for all IDs) #321

Merged
merged 8 commits into from
Feb 7, 2022

Conversation

CMCDragonkai
Copy link
Member

@CMCDragonkai CMCDragonkai commented Jan 28, 2022

Description

Boundary validation seems like a general problem that people are hitting over and over. It's best to fix this once and for all across all domains to ensure that we always have a consistent way of doing boundary validation.

#318 was merged with problems involving general data validation.

It's come to attention that data validation is not consistently applied across all of the IO boundaries that PK does.

Data validation is a very crucial task that we've missed, I originally thought that the combination of GRPC protobuf and the domain types would be enough. But it appears it is not, and our IO boundaries require a general validation system.

I've used general validation systems/libraries in the past for many different applications, mostly web SaaS, and I've never liked any of them.

After working with JSON.parse, I got some inspiration that combines the ideas from JSON.parse's reviver parameter and the usage of recursion schemes used in functional languages, and the boundary issue mentioned in the Yesod framework https://www.yesodweb.com/book/introduction to create a parse function that do triple duty:

  1. Be capable of validating nested data structure without any static constraints
  2. Be capable of doing IO during validation, in order to validate complex dynamic data relationships
  3. Be capable of sanitisation and type-marshalling and any other transformations that could occur

The lack of tuple structures, tuple-based pattern matching in JS means the composition of graph-operating function fragments is less syntactically nice, however the general principle of recursion schemes is followed.

Example prototype:

import { CustomError } from 'ts-custom-error';

class ErrorParse extends CustomError {
  public readonly errors: Array<ErrorRule>;
  constructor(errors: Array<ErrorRule>) {
    const message = errors.map((e) => e.message).join('; ');
    super(message);
    this.errors = errors;
  }
}

class ErrorRule extends CustomError {
  public keyPath: Array<string>;
  public value: any;
  public context: object;
  constructor(message?: string) {
    super(message);
  }
}

/**
 * Functional data-validation, inspired by recursion schemes and `JSON.parse` reviver
 * Post-fix depth first traversal on the data structure tree
 * Capable of modifying the data structure during traversal
 * Works like `JSON.parse` but on POJOs rather than JSON strings
 * Compose validation rules into the `rules` function
 */
async function parse(
  rules: (keyPath: Array<string>, value: any) => Promise<any>,
  data: any,
  options: { mode: 'greedy' | 'lazy' } = { mode: 'lazy' }
): Promise<any> {
  const errors: Array<ErrorRule> = [];
  const parse_ = async (keyPath: Array<string>, value: any, context: object) => {
    if (typeof value === 'object' && value != null) {
      for (const key in value) {
        value[key] = await parse_([...keyPath, key], value[key], value);
      }
    }
    try {
      value = await rules.bind(context)(keyPath, value);
    } catch (e) {
      if (e instanceof ErrorRule) {
        e.keyPath = keyPath;
        e.value = value;
        e.context = context;
        errors.push(e);
        // If lazy mode, short circuit evaluation
        // And throw the error up
        if (options.mode === 'lazy') {
          throw e;
        }
      } else {
        throw e;
      }
    }
    return value;
  };
  try {
    // The root context is an object containing the root data but keyed with undefined
    data = await parse_([], data, { undefined: data });
  } catch (e) {
    if (e instanceof ErrorRule) {
      throw new ErrorParse(errors);
    } else {
      throw e;
    }
  }
  if (errors.length > 0) {
    throw new ErrorParse(errors);
  }
  return data;
}

export { parse };

The rules parameter is a generic function that operates over every element of the data structure graph "bottom-up" using a post-fix depth first search traversal order, this allows one to operate any point in the graph, and the rules are meant to be composed of pattern-matched fragments doing a flexible mix of validation, sanitisation and marshalling.

The exact implementation may change as I encounter problems.

Tasks

  1. The decodeNodeId should be returning undefined and the exception for invalid node id should be thrown on the boundaries, not inside the decodeNodeId function. The decoding function needs to match the behaviour that other utilities work with.
  2. [ ] ErrorNodeIdInvalid over ErrorInvalidNodeId to keep it consistent with the rest of the code as well as changing the error message to remove ..
  3. Implement validation domain with validate and validateSync and the usage of ErrorParse for generically throwing validation exceptions
  4. Change all validation functions to type predicates so that they assert the resulting type:
    • isValidHost - isHost
    • isValidHostname - isHostname
    • isPort
    • isGestaltAction
    • isVaultAction
  5. Ensure that all relevant encoded ids are done in their domains like nodes/utils.ts - encodeNodeId, decodeNodeId, vaults/utils.ts - encodeVaultId, decodeVaultId. Any Id that requires an encoded form and a decoded form should do this.
    • What about NotificationId and Claimid and Permid?
      • NotificationId and ClaimId are internal only
      • ClaimId is exposed externally as ClaimIdString, this will become ClaimIdEncoded, and encoded as base32hex instead of base58btc to preserve lexicographic order
      • Both NotificationId and PermissionId may have a "binary string" type NotificationIdString and PermissionIdString, this is different from any encoded form which would be NotifcationIdEncoded and PermissionIdEncoded, and there's no encoding/decoding functions, the string from happens during POJO keys usually.
      • Only PermissionId needs PermissionIdString for now. In the future NotificationId can have NotificationIdString if is being used.
    • Make sure all decoding functions are functional in that they return IdType | undefined, they are not supposed to throw exceptions, and for domains where the id is being extracted from a "trusted source of truth", then the decoding is expected to succeed, then using ! can be used
      • NodeManager - from the node graph
      • GestaltGraph - from the graph db
      • NotificationManager - from the notifications db
    • CLI bin parsers may require a refactoring to apply validation consistently to arguments and options, and argument should then have the relevant types afterwards. It's important to resolve any default values must be the value that would be parsed.
  6. Service handlers are IO boundaries, all service handlers (client and agent) must apply the new validate or validateSync from validation domain and validate their input data. Remember validate and validateSync represent the 2nd line of defense. The first line for API is protobuf decoding. And for CLI, that would be commander's arg parsing logic.
  7. Ensure that ErrorValidation that is thrown by validate or validateSync can be serialised over GRPC and deserialised on the client side to see all parse errors. The list of errors during validation should be available under the data property as data.errors = [ { message, keyPath, value }]
  8. Ensure that ErrorValidation can be pretty printed to the STDERR by our root exception handlers, we want to be able to show a multitude of errors, however most uses of validate and validateSync will be lazy and not greedy in order to avoid Denial of Service.
  9. See if this applies to CLI bin commands too
    • The validation utilities don't apply to CLI bin parsers, because they sit in their own context of being used as arg parsers, however they do use the encoding/decoding and type guards provided by the domains
    • Some parsers are moved into validation/utils.ts like parseGestaltId, this was changed to actually return the GestaltId. Future parsing utilities to be shared between service handlers and CLI parsers should be delegated to validation/utils.ts, the main thing is to catch the ErrorParse and rethrow as a commander error.
  10. See if this applies to file interactions like Status or elsewhere
    • For Status, it uses the decodeNodeId directly because it already has a general error for failure to parse which is ErrorStatusParse, so it doesn't use the validation utility
    • Other files in the future may end up using the validation system as well, but for now, no other files are being read have sophisticated validation requirements, I imagine for some files it will just be done in the CLI bin commands when it gets read, so future data processing will use the general validation system
  11. Apply tests to validation domain, and new tests for expecting ErrorValidation.
  12. Check if we need to rework our imports to prevent cycles when imports from the index vs directly importing. We know this causes problems when some modules are broken. But does this actually cause cycles during imports too? If so, we have to change to direct imports.

Final checklist

  • Domain specific tests
  • Full tests
  • Updated inline-comment documentation
  • Lint fixed
  • Squash and rebased
  • Sanity check the final build

src/nodes/errors.ts Outdated Show resolved Hide resolved
src/nodes/errors.ts Outdated Show resolved Hide resolved
@CMCDragonkai
Copy link
Member Author

All these places will need to be updated with boundary validation logic:

[nix-shell:~/Projects/js-polykey/src]$ ag 'decodeNodeId'
vaults/VaultManager.ts
360:            nodesUtils.decodeNodeId(nodes[node].id),
365:            nodesUtils.decodeNodeId(nodes[node].id),

gestalts/utils.ts
67:  return nodesUtils.decodeNodeId(node.nodeId);

gestalts/GestaltGraph.ts
286:            nodesUtils.decodeNodeId(gId.nodeId),
321:      nodesUtils.decodeNodeId(nodeInfo.id),
333:          nodesUtils.decodeNodeId(nodeInfo.id),
395:            nodesUtils.decodeNodeId(gId.nodeId),
440:      nodesUtils.decodeNodeId(nodeInfo.id),
492:          nodesUtils.decodeNodeId(nodeInfo.id),
513:        nodesUtils.decodeNodeId(nodeInfo.id),
525:          nodesUtils.decodeNodeId(nodeInfo.id),
534:            nodesUtils.decodeNodeId(nodeInfo.id),
550:            nodesUtils.decodeNodeId(nodeInfo.id),
605:    const nodeIdEncoded1 = nodesUtils.decodeNodeId(nodeInfo1.id);
606:    const nodeIdEncoded2 = nodesUtils.decodeNodeId(nodeInfo2.id);

bin/utils/parsers.ts
125:    const seedNodeId = nodesUtils.decodeNodeId(idHostPort[0]);

bin/utils/processors.ts
224:    nodeId: nodesUtils.decodeNodeId(nodeId),
267:    nodeId != null ? nodesUtils.decodeNodeId(nodeId) : undefined;

notifications/NotificationsManager.ts
210:        nodesUtils.decodeNodeId(notification.senderId),
299:        nodesUtils.decodeNodeId(notification.senderId).equals(fromNode)

network/utils.ts
172:  return nodesUtils.decodeNodeId(commonName.value);

network/ForwardProxy.ts
306:        ? nodesUtils.decodeNodeId(nodeIdEncodedForURL)

nodes/NodeConnection.ts
274:      const nodeId: NodeId = nodesUtils.decodeNodeId(nodeIdEncoded);

nodes/utils.ts
74:function decodeNodeId(nodeIdEncoded: string): NodeId | undefined {
92:  decodeNodeId,

nodes/NodeManager.ts
318:        const endNodeId = nodesUtils.decodeNodeId(payload.data.node2);
405:      nodesUtils.decodeNodeId(message.getTargetId()),
408:      nodesUtils.decodeNodeId(message.getSrcId()),
409:      nodesUtils.decodeNodeId(message.getTargetId()),

discovery/Discovery.ts
116:        const nodeId = nodesUtils.decodeNodeId(vertexGId.nodeId);
153:            const linkedVertexNodeId = nodesUtils.decodeNodeId(
227:          const linkedVertexNodeId = nodesUtils.decodeNodeId(data.node);
319:        await this.nodeManager.getPublicKey(nodesUtils.decodeNodeId(data.node)),

status/Status.ts
324:    if (key === 'nodeId') return nodesUtils.decodeNodeId(value);

client/service/nodesAdd.ts
32:      const nodeId = nodesUtils.decodeNodeId(call.request.getNodeId());

client/service/gestaltsActionsUnsetByNode.ts
28:      const nodeId = nodesUtils.decodeNodeId(info.getNode()!.getNodeId());

client/service/gestaltsActionsSetByNode.ts
28:      const nodeId = nodesUtils.decodeNodeId(info.getNode()!.getNodeId());

client/service/gestaltsDiscoveryByNode.ts
26:      const nodeId = nodesUtils.decodeNodeId(info.getNodeId()!);

client/service/gestaltsGestaltGetByNode.ts
24:      const nodeId = nodesUtils.decodeNodeId(call.request.getNodeId()!);

client/service/vaultsScan.ts
20:    // Const possibleNodeId = nodesUtils.decodeNodeId(call.request.getNodeId());

client/service/gestaltsActionsGetByNode.ts
25:      const nodeId = nodesUtils.decodeNodeId(info.getNodeId());

client/service/notificationsSend.ts
25:      const receivingId = nodesUtils.decodeNodeId(call.request.getReceiverId());

client/service/nodesClaim.ts
33:      const remoteNodeId = nodesUtils.decodeNodeId(call.request.getNodeId());

client/service/nodesPing.ts
27:      const nodeId = nodesUtils.decodeNodeId(call.request.getNodeId());

client/service/nodesFind.ts
29:      const nodeId = nodesUtils.decodeNodeId(nodeIdEncoded);

agent/service/nodesClosestLocalNodesGet.ts
22:      const targetNodeId = nodesUtils.decodeNodeId(call.request.getNodeId());

agent/service/nodesCrossSignClaim.ts
70:          nodesUtils.decodeNodeId(payloadData.node1),

agent/service/nodesHolePunchMessageSend.ts
25:        nodesUtils.decodeNodeId(call.request.getTargetId())
35:          nodesUtils.decodeNodeId(call.request.getSrcId()),

@CMCDragonkai
Copy link
Member Author

Validation errors are going to go into their own domain as they may be shared between:

  1. bin
  2. agent/service
  3. client/service

This problem has come up too often. Will be coming up a way to share validation errors, and work with the existing domains as well.

@CMCDragonkai
Copy link
Member Author

@tegefaulkes don't use private scope. They are deprecated. We always use protected scope. And very rarely use the new # qualitifier for private properties. And in fact we never have to use private properties atm.

@CMCDragonkai
Copy link
Member Author

CMCDragonkai commented Jan 28, 2022

@tegefaulkes there's no need to use the replacer parameter because toJSON is automatic for all NodeId objects.

Actually the Status does require it simply due to the string version rather than the JSON representation.

@CMCDragonkai
Copy link
Member Author

CMCDragonkai commented Jan 28, 2022

Here's an idea. We are going to take inspiration from JSON.parse reviver parameter.

We can have these functions:

async function validate(parse, data): Promise<data | undefined> {

}

The validate function walks over the data and applies the check to it. This means check can return a set of possible values for any given part of the data tree. We know that data may be a nested recursive structure, and the parse function runs on each individual element.

The parse function may be passed several properties similar to reviver:

If a reviver is specified, the value computed by parsing is transformed before being returned. Specifically, the computed value and all its properties (beginning with the most nested properties and proceeding to the original value itself) are individually run through the reviver. Then it is called, with the object containing the property being processed as this, and with the property name as a string, and the property value as arguments. If the reviver function returns undefined (or returns no value, for example, if execution falls off the end of the function), the property is deleted from the object. Otherwise, the property is redefined to be the return value.

Here this would mean that parse if it returns undefined, the property is deleted from the object. If the parse returns a new value, that value replaces the original in data. If parse throws an exception we have a couple options.

First we have to understand JSON.parse traversal order. In the case of JSON.parse, it says it begins with the most nested property, and then proceeding to the original root value. I suppose it is "post-order depth first search". Note that at the root the key will be '' and not undefined, because it is expected JSON keys are always strings.

If we want to accumulate exceptions across the data structure. An exception on the lower part of the tree may be accumulated with an exception at a higher part of the tree.

So if we have a data structure like:

const data = {
  a: 'b',
  c: {
    d: 'e'
  }
};

Then if d: 'e' were to be changed to d: 4, then when c gets visited, then it would get { d: 4 } as the value. If this is also invalid, then an exception may also be thrown here. Then validate can accumulate all of these exceptions together to present a list of exceptions.

There are some changes required to make this possible. The parser must be passed the keyPath not just key. This enables one to know where one is in the tree. The keyPath can be Array<string> since all POJO keys are always strings. An empty array can indicate the same thing as an empty string for the key as this is only possible for JSON.parse at the root.

Furthermore unlike JSON.parse, we don't parse strings, we are validating objects.

Instead of accumulating exceptions, we can also fail fast, and throw on the first exception rather than continuing to walk the tree. So different variants can be made for this:

validate(parser, data, { mode: 'lazy' | 'greedy' });

The result is therefore: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError.

One additional issue is matching validation errors to the location in the data. This is the choice of the parser, since all the information is available to the parser function to add this information to the errors. Alternatively we can provide this additional information in the AggregateError providing extra metadata per error. Primarily it would be the key path where the exception occurred.

If no exceptions are thrown by the parser, then validate returns the transformed data if it was transformed and not just checked.

This should enable us to use validate on single values like NodeIdEncoded and on nested structures to validate a POJO.

This can be put into our utils as validate and validateSync.

I like the word parse instead, so you can do parse(rules, data) which is more correct than validate as parse can imply transformation of the data.

@CMCDragonkai
Copy link
Member Author

Existence checking can be done by having the rules function check the root of the data object, and then checking if a particular key exists in the value.

The return type of parse is any, thus requiring a typecast. This is because we can't statically figure out how the rules function may transform the data. Primarily because the rules function doesn't return the full data type, and may not deal with it at all. This is similar to recursion schemes allowing one to write small functions that only operate on specific fragments of the full data structure.

@CMCDragonkai
Copy link
Member Author

At the boundaries of the program we would then do something like:

const nodeIdEncoded = call.request.getNodeId();

// assuming synchronous parse
const nodeId = parse(
  (keyPath, value) => {
    value = nodesUtils.decodeNodeId(value);
    if (value == null) {
      throw new nodesErrors.ErrorNodeIdInvalid();
    }
    return value;
  }, 
  data
);

The location of exceptions is still domain specific, as they should be close to where the utility functions that decode them are, even if those functions aren't themselves throwing those exceptions.

This generalises to more sophisticated data structures as well.

@CMCDragonkai
Copy link
Member Author

For alot of boundaries, full data input parsing isn't really done, it seems done in piecemeal one at a time. And so validation exceptions end up being thrown in a number of places that later interrupt the call. So in the future, this parse can be used to "frontload" all data validation to our boundaries, and we have alot of boundaries!

@CMCDragonkai
Copy link
Member Author

The lack of frontloading, and general validation errors has meant that we end up with small exceptions like ErrorHostInvalid, that would usually have been part of a general validation exception that is passed to the end user. This general validation exception would have been a generic data structure that keeps track of all validation errors.

This smells bad, right now there's an ErrorHostInvalid (changed from ErrorInvalidHost inside nodes/errors.ts that is only being thrown in client/service/nodesAdd), it has no relationship to the rest of the nodes domain at all.

Which should tell us that in the case of parse not all errors should be an exception necessarily, or at least just generic JS exceptions are fine too like RangeError or ValueError.

The parse may be throwing a ParseError which extend the AggregateError and become this generic validation exception container, and exceptions like ErrorHostInvalid can be removed in favour of generic simple errors like RangeError or TypeError or ReferenceError in https://developer.mozilla.org/en-US/docs/web/javascript/reference/global_objects/error.

This should avoid us having to create specific exceptions for every little validation error that could occur. This means exceptions like ErrorNodeIdInvalid and ErrorHostInvalid are all removed, and instead, we will be getting a ParseError which the CLI can properly format in its STDERR, and the GUI can properly format in its error reporting.

@CMCDragonkai
Copy link
Member Author

Using AggregateError requires using at least Node 15, since it's not yet in Node 14.

The node2nix has finally updated to support v15 and v16, however nixpkgs is stil using the old node2nix: NixOS/nixpkgs#146440.

So for now I just have to use a custom ParseError instead of extending AggregateError.

I was also thinking that given that TypeError and can be used, instead we have a custom exception just for this called RuleError. That way one can do:

parse((keyPath, value) => {
  throw new RuleError(keyPath);
}, data);

Alternatively we don't pass in keyPath and expect it to be augmented by parse. This makes it similar to the validation functions that exist in the commander CLI, which means any other exception we will throw.

Finally the result should be a ParseError that contains all these rule errors

@CMCDragonkai
Copy link
Member Author

CMCDragonkai commented Jan 30, 2022

A note the this parameter doesn't appear to work when passing an arrow function into JSON.parse as the reviver. Not sure if this is because the this binding isn't done properly or if an explicit bind would work better for our rules callback.


Yes arrow functions cannot have this be bound. Only normal function callbacks. This is fine. To enable the ability to mutate each value in the object I'll need pass the context around in the inner parse_ worker.

@CMCDragonkai
Copy link
Member Author

First attempt:

async function parse(
  rules: (keyPath: Array<string>, value: any) => Promise<any>,
  data: any,
  options: { mode: 'greedy' | 'lazy' } = { mode: 'lazy' }
): Promise<any> {
  const errors: Array<ErrorRule> = [];
  const parse_ = async (keyPath: Array<string>, value: any, context: any) => {
    if (typeof value === 'object' && value != null) {
      for (const key in value) {
        value[key] = await parse_([...keyPath, key], value[key], value);
      }
    }
    try {
      value = await rules.bind(context)(keyPath, value);
    } catch (e) {
      if (e instanceof ErrorRule) {
        e.keyPath = keyPath;
        e.value = value;
        errors.push(e);
      } else {
        throw e;
      }
    }
    return value;
  };
  // The root context is an object containing the root data keyed by empty string
  // This matches JSON.parse behaviour
  data = await parse_([], data, {'': data});
  return data;
}

@CMCDragonkai
Copy link
Member Author

Full example:

import { CustomError } from 'ts-custom-error';

class ErrorParse extends CustomError {
  public readonly errors: Array<ErrorRule>;
  constructor(errors: Array<ErrorRule>) {
    const message = errors.map((e) => e.message).join('; ');
    super(message);
    this.errors = errors;
  }
}

class ErrorRule extends CustomError {
  public keyPath: Array<string>;
  public value: any;
  public context: object;
  constructor(message: string | undefined) {
    super(message);
  }
}

async function parse(
  rules: (keyPath: Array<string>, value: any) => Promise<any>,
  data: any,
  options: { mode: 'greedy' | 'lazy' } = { mode: 'lazy' }
): Promise<any> {
  const errors: Array<ErrorRule> = [];
  const parse_ = async (keyPath: Array<string>, value: any, context: object) => {
    if (typeof value === 'object' && value != null) {
      for (const key in value) {
        value[key] = await parse_([...keyPath, key], value[key], value);
      }
    }
    try {
      value = await rules.bind(context)(keyPath, value);
    } catch (e) {
      if (e instanceof ErrorRule) {
        e.keyPath = keyPath;
        e.value = value;
        e.context = context;
        errors.push(e);
        // If lazy mode, short circuit evaluation
        // And throw the error up
        if (options.mode === 'lazy') {
          throw e;
        }
      } else {
        throw e;
      }
    }
    return value;
  };
  try {
    // The root context is an object containing the root data but keyed with undefined
    data = await parse_([], data, { undefined: data });
  } catch (e) {
    if (e instanceof ErrorRule) {
      throw new ErrorParse(errors);
    } else {
      throw e;
    }
  }
  if (errors.length > 0) {
    throw new ErrorParse(errors);
  }
  return data;
}

async function main () {
  try {
    const output = await parse(
      function (keyPath, value) {
        switch (keyPath.join('.')) {
          case 'name':
            if (typeof value !== 'string' || value.length < 3) {
              throw new ErrorRule('name must be 3 characters or longer');
            }
            break;
          case 'age':
            if (typeof value !== 'number') {
              throw new ErrorRule('age must be a number');
            }
            break;
        }
        return value;
      },
      {
        name: 'bc',
        age: 'a',
      },
      { mode: 'greedy' }
    );
    console.log(output);
  } catch (e) {
    console.log(e);
  }
}

main();

@CMCDragonkai
Copy link
Member Author

Usage at a nodesAdd boundary looks like this. (Note that more tweaks needed to ensure that we get a clean way of reporting errors to the end-user).

function nodesAdd({
  nodeManager,
  authenticate,
}: {
  nodeManager: NodeManager;
  authenticate: Authenticate;
}) {
  return async (
    call: grpc.ServerUnaryCall<nodesPB.NodeAddress, utilsPB.EmptyMessage>,
    callback: grpc.sendUnaryData<utilsPB.EmptyMessage>,
  ): Promise<void> => {
    const response = new utilsPB.EmptyMessage();
    try {
      const metadata = await authenticate(call.metadata);
      call.sendMetadata(metadata);
      const data: { nodeId: NodeId, host: Host | Hostname, port: Port } = await parse(
        (keyPath, value) => {
          switch (keyPath.join('.')) {
            case 'nodeId':
              value = nodesUtils.decodeNodeId(value);
              if (value == null) {
                throw new utilsErrors.ErrorUtilsRule('nodeId is invalid');
              }
              break;
            case 'host':
              if (networkUtils.isValidHost(value)) {
                throw new utilsErrors.ErrorUtilsRule('host is invalid');
              }
              break;
            case 'port':
              if (value == null) {
                throw new utilsErrors.ErrorUtilsRule('port is invalid');
              }
              break;
          }
          return value;
        },
        {
          nodeId: call.request.getNodeId(),
          host: call.request.getAddress()?.getHost(),
          port: call.request.getAddress()?.getPort()
        }
      );
      await nodeManager.setNode(
        data.nodeId,
        {
          host: data.host,
          port: data.port,
        } as NodeAddress
      );
      callback(null, response);
      return;
    } catch (err) {
      callback(grpcUtils.fromError(err), null);
      return;
    }
  };
}

@CMCDragonkai CMCDragonkai changed the title WIP: NodeId Fixes - decodeNodeId should be returning undefined and ErrorNodeIdInvalid usage at the boundaries WIP: General Data Validation - Boundary IO locations should be doing data validation and marshalling Jan 31, 2022
@CMCDragonkai
Copy link
Member Author

Exceptions like:

  • ErrorInvalidNodeId
  • ErrorInvalidHost
  • ErrorInvalidId
  • ErrorClientInvalidProvider

And potentially more can all be removed. The parse above works asynchronously so business rules can be applied asynchronously inside each rule, this can mean checking provider ids, or valid vault ids... etc.

@emmacasolin since you've been doing test splitting, can you comment on any other one-off exceptions and data validation exceptions that we can replace with the generic parse system.

@tegefaulkes @joshuakarp

@emmacasolin
Copy link
Contributor

@emmacasolin since you've been doing test splitting, can you comment on any other one-off exceptions and data validation exceptions that we can replace with the generic parse system.

Not from test splitting, but the only thing I can think of that hasn't been mentioned yet is the errors from JSON schema validation, which is done for notifications and claims.

/**
 * Exceptions raised when validating a Notification against a JSON schema
 */
class ErrorSchemaValidate extends ErrorNotifications {}

class ErrorNotificationsInvalidType extends ErrorSchemaValidate {}

class ErrorNotificationsGeneralInvalid extends ErrorSchemaValidate {}

class ErrorNotificationsGestaltInviteInvalid extends ErrorSchemaValidate {}

class ErrorNotificationsVaultShareInvalid extends ErrorSchemaValidate {}

class ErrorNotificationsValidationFailed extends ErrorSchemaValidate {}
/**
 * Exceptions arising during schema validation
 */
class ErrorSchemaValidate extends ErrorClaims {}

class ErrorClaimValidationFailed extends ErrorSchemaValidate {}

class ErrorNodesClaimType extends ErrorSchemaValidate {}

class ErrorIdentitiesClaimType extends ErrorSchemaValidate {}

class ErrorSinglySignedClaimNumSignatures extends ErrorSchemaValidate {}

class ErrorDoublySignedClaimNumSignatures extends ErrorSchemaValidate {}

class ErrorSinglySignedClaimValidationFailed extends ErrorSchemaValidate {}

class ErrorDoublySignedClaimValidationFailed extends ErrorSchemaValidate {}

@CMCDragonkai
Copy link
Member Author

Schema validation is fine, that won't change. We're more interested in these one off data validation not caught by the CLI arg parsing or from protobuf when going through client service.

CLI arg parsing is like a thin layer on top before reaching the general validation system. But there is overlap in duties.

@CMCDragonkai
Copy link
Member Author

https://stackoverflow.com/q/42819408/582917

Turns out one can also just extend the Json schema system with custom validators. Then we can just reuse the Json schema validator for general data validation.

Even Async validator works. https://ajv.js.org/guide/async-validation.html

@CMCDragonkai
Copy link
Member Author

This is a good reference for nested object reference in the future: https://stackoverflow.com/questions/6393943/convert-a-javascript-string-in-dot-notation-into-an-object-reference.

Ok so if json schema can do this as well, we might as well extend our existing json schema to apply in these situations. It looks like the only thing we need to do is add custom keywords to the ajv instance.

const ajv = new Ajv()

ajv.addKeyword({
  keyword: "idExists",
  async: true,
  type: "number",
  validate: checkIdExists,
})

This means such keywords are shared across the same AJV instance.

One question is how much sharing do we expect. It does seem verbose that every time we are doing validation we will need to do this in our client handlers:

const ajv = new Ajv();
ajv.addKeyword({ ... });
const schema = { ... }
cont validate = ajv.compile(schema);
try {
  await validate(data);
} catch (e) {
  // handle error
}

There's just a lot of boilerplate to setup. This is why in our domains that have json schemas, we have done all the setup prior, and then just export the validation function. However doing this for every possible location where we want to do data validation seems poor form, since there can be lots of one-off validations that is only relevant to the specific API end-point.

So therefore to save on boilerplate, what we could do is share an ajv instance with all the keywords already loaded. Then just expect service handlers to do:

const validate = ajv.compile({ .. });
try {
  ajv.compile({ ... })(data);
} catch (e) {
}
// use the data

Now modifying/sanitising data is also possible with the custom keywords: https://ajv.js.org/guide/modifying-data.html

@CMCDragonkai
Copy link
Member Author

Some of the format rules that jsonschema exposes has to be used carefully to prevent Denial of Service attacks: https://ajv.js.org/security.html

Mostly it's about setting the max length for any string validators, and also failing on the first exception.

@CMCDragonkai
Copy link
Member Author

Bringing in the library https://github.com/ajv-validator/ajv-formats so we can make use of the standard formats that is defined in JSON schema: https://github.com/ajv-validator/ajv-formats

@CMCDragonkai
Copy link
Member Author

Current schemas:

[nix-shell:~/Projects/js-polykey/src]$ find . -name '*.json' | xargs wc -l
  89 ./claims/ClaimNodeSinglySigned.json
  94 ./claims/ClaimIdentity.json
  89 ./claims/ClaimNodeDoublySigned.json
  12 ./notifications/GestaltInvite.json
  22 ./notifications/VaultShare.json
  74 ./notifications/Notification.json
  15 ./notifications/General.json
  76 ./status/StatusSchema.json
 471 total

@emmacasolin @joshuakarp I believe these should be renamed to have a the Schema prefix like ./status/StatusSchema.json. See how in status/utils.ts we are loading it.

@CMCDragonkai
Copy link
Member Author

I just tried to use JSON schema to do these things. It's actually too complicated.

Adding custom keywords requires all sorts of configuration, and AJV is very complicated. I think we stick with how parse is working, and leave AJV to be used for when we have explicit JSON schema documents. Our API service handlers don't directly work against JSON schema documents.

Let's say Protobuf and JsonSchema is used as the "first-line" of validation. Doing purely syntactic validation.

Logical validation can occur on a second line, using a more flexible parse function defined above.

@CMCDragonkai
Copy link
Member Author

CMCDragonkai commented Feb 7, 2022

@joshuakarp @tegefaulkes to look over the test failures and fix them up. Mostly in the bin tests right now:

  1. tests/bin/agent - @joshuakarp
  2. tests/bin/identities
  3. tests/bin/nodes - @tegefaulkes

Other timeout errors may be fixed if those initial things get fixed. Please organise among yourselves how to fix the rest.

@CMCDragonkai
Copy link
Member Author

There's a few places where I put a useless await:

[nix-shell:~/Projects/js-polykey/src]$ ag 'await validateSync'
client/service/notificationsSend.ts
31:      } = await validateSync(

client/service/nodesClaim.ts
38:      } = await validateSync(

client/service/nodesPing.ts
33:      } = await validateSync(

client/service/nodesFind.ts
35:      } = await validateSync(

client/service/gestaltsActionsSetByIdentity.ts
35:      } = await validateSync(

agent/service/nodesClosestLocalNodesGet.ts
29:      } = await validateSync(

agent/service/nodesHolePunchMessageSend.ts
28:      } = await validateSync(

Removing these.

@CMCDragonkai
Copy link
Member Author

@tegefaulkes @joshuakarp once you push up test fixes as WIP commits. I can lint, rebase and squash those back into the 8 commits I have spreading them into the correct commits.

@joshuakarp
Copy link
Contributor

joshuakarp commented Feb 7, 2022

Running all domains:

  • - acl
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/acl

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/acl"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-xRlPpG
 PASS  tests/acl/utils.test.ts
  acl/utils
    ✓ merging permissions (3 ms)

 PASS  tests/acl/ACL.test.ts (14.657 s)
  ACL
    ✓ acl readiness (41 ms)
    ✓ trust and untrust gestalts (41 ms)
    ✓ setting and unsetting vault actions (26 ms)
    ✓ joining existing gestalt permissions (11 ms)
    ✓ joining existing vault permissions (21 ms)
    ✓ node removal (8 ms)
    ✓ vault removal (15 ms)
    ✓ transactional operations (26 ms)

Test Suites: 2 passed, 2 total
Tests:       9 passed, 9 total
Snapshots:   0 total
Time:        15 s, estimated 21 s
Ran all test suites matching /tests\/acl/i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-xRlPpG
  • - agent
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/agent/

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/agent/"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-yPKnVr
 PASS  tests/agent/GRPCClientAgent.test.ts (17.213 s)
  GRPCClientAgent
    ✓ GRPCClientAgent readiness (1099 ms)
    ✓ echo (988 ms)
    ✓ Can connect over insecure connection. (973 ms)
    ○ skipped can check permissions
    ○ skipped can scan vaults
    Cross signing claims
      ✓ can successfully cross sign a claim (1926 ms)
      ✓ fails after receiving undefined singly signed claim (1848 ms)
      ✓ fails after receiving singly signed claim with no signature (1871 ms)

Test Suites: 1 passed, 1 total
Tests:       2 skipped, 6 passed, 8 total
Snapshots:   0 total
Time:        17.246 s, estimated 21 s
Ran all test suites matching /tests\/agent\//i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-yPKnVr

  • - bin: failures in:
    • /agent
    • /identities
    • /nodes
agent
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/bin/agent/          

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/bin/agent/"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-G6IaKA
 PASS  tests/bin/agent/lockall.test.ts (55.091 s)
  lockall
    ✓ lockall deletes the session token (980 ms)
    ✓ lockall ensures reauthentication is required (1802 ms)
    ✓ lockall causes old session tokens to fail (1478 ms)

 PASS  tests/bin/agent/unlock.test.ts (56.405 s)
  unlock
    ✓ unlock acquires session token (783 ms)

 PASS  tests/bin/agent/lock.test.ts (58.853 s)
  lock
    ✓ lock deletes the session token (729 ms)
    ✓ lock ensures reauthentication is required (1360 ms)

 FAIL  tests/bin/agent/status.test.ts (60.536 s)
  status
    ✓ status on STARTING, STOPPING, DEAD agent (17911 ms)
    ✓ status on missing agent (11 ms)
    status with global agent
      ✓ status on LIVE agent (838 ms)
      ✕ status on remote LIVE agent (19 ms)

  ● status › status with global agent › status on remote LIVE agent

    expect(received).toBe(expected) // Object.is equality

    Expected: 0
    Received: 64

      203 |         '--verbose',
      204 |       ]);
    > 205 |       expect(exitCode).toBe(0);
          |                        ^
      206 |       expect(JSON.parse(stdout)).toMatchObject({
      207 |         status: 'LIVE',
      208 |         pid: expect.any(Number),

      at Object.<anonymous> (tests/bin/agent/status.test.ts:205:24)

 PASS  tests/bin/agent/stop.test.ts (85.593 s)
  stop
    ✓ stop LIVE agent (30831 ms)
    ✓ stopping is idempotent during concurrent calls and STOPPING or DEAD status (9954 ms)
    ✓ stopping starting agent results in error (3972 ms)
    ✓ stopping while unauthenticated does not stop (8275 ms)

 FAIL  tests/bin/agent/start.test.ts (144.194 s)
  start
    ✓ start in foreground (19329 ms)
    ✓ start in background (6257 ms)
    ✓ concurrent starts results in 1 success (3777 ms)
    ✓ concurrent with bootstrap results in 1 success (2601 ms)
    ✓ start with existing state (5712 ms)
    ✓ start when interrupted, requires fresh on next start (9286 ms)
    ✓ start from recovery code (14267 ms)
    ✕ start with network configuration (40015 ms)
    start with global agent
      ✓ start with seed nodes option (32 ms)
      ✓ start with seed nodes environment variable (11 ms)

  ● start › start with network configuration

    : Timeout - Async callback was not invoked within the 40000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 40000 ms timeout specified by jest.setTimeout.Error:

      615 |     global.defaultTimeout * 3,
      616 |   );
    > 617 |   test(
          |   ^
      618 |     'start with network configuration',
      619 |     async () => {
      620 |       const status = new Status({

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/bin/agent/start.test.ts:617:3)

A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks.
Test Suites: 2 failed, 4 passed, 6 total
Tests:       2 failed, 22 passed, 24 total
Snapshots:   0 total
Time:        145.167 s
Ran all test suites matching /tests\/bin\/agent\//i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-G6IaKA
npm ERR! Test failed.  See above for more details.
identities
/identities:
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/bin/identities/ 

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/bin/identities/"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-sqdL5Y
 FAIL  tests/bin/identities/identities.test.ts (235.404 s)
  CLI Identities
    commandAllowGestalts
      ✕ Should allow permissions on node. (20029 ms)
      ✕ Should allow permissions on Identity. (49 ms)
      ✕ Should fail on invalid inputs. (20017 ms)
    commandDisallowGestalts
      ✕ Should disallow permissions on Node. (20032 ms)
      ✕ Should disallow permissions on Identity. (59 ms)
      ✕ Should fail on invalid inputs. (20027 ms)
    commandPermissionsGestalts
      ✕ Should get permissions on Node. (20089 ms)
      ✕ Should get permissions on Identity. (42 ms)
    commandTrustGestalts
      ✕ Should set trust on Node. (20021 ms)
      ✕ Should set trust on Identity. (33 ms)
      ✕ Should fail on invalid inputs. (20022 ms)
    commandUntrustGestalts
      ✕ Should unset trust on Node. (20045 ms)
      ✕ Should unset trust on Identity. (41 ms)
      ✕ Should fail on invalid inputs. (20022 ms)
    commandClaimIdentity
      ✓ Should claim an identity. (67 ms)
      ✓ Should fail for unauthenticated identities. (30 ms)
    commandAuthenticateProvider
      ✓ Should authenticate an identity with a provider. (50 ms)
    commandGetGestalts
      ✕ Should list gestalt by Node (20018 ms)
      ✕ Should list gestalt by Identity (33 ms)
    commandListGestalts
      ✓ Should list gestalts with permissions. (102 ms)
    commandSearchIdentities
      ✓ Should find a connected identity. (35 ms)
    commandDiscoverGestalts
      ✕ Should start discovery by Node (20015 ms)
      ✕ Should start discovery by Identity (31 ms)

  ● CLI Identities › commandAllowGestalts › Should allow permissions on node.

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      154 |   // Tests
      155 |   describe('commandAllowGestalts', () => {
    > 156 |     test('Should allow permissions on node.', async () => {
          |     ^
      157 |       const commands = genCommands(['allow', node1.id, 'notify']);
      158 |       const result = await testBinUtils.pkStdio(commands, {}, dataDir);
      159 |       expect(result.exitCode).toBe(0); // Succeeds.

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/bin/identities/identities.test.ts:156:5)

  ● CLI Identities › commandAllowGestalts › Should allow permissions on Identity.

    expect(received).toBe(expected) // Object.is equality

    Expected: 0
    Received: 255

      187 |       ]);
      188 |       const result = await testBinUtils.pkStdio(commands, {}, dataDir);
    > 189 |       expect(result.exitCode).toBe(0); // Succeeds.
          |                               ^
      190 |
      191 |       const actions =
      192 |         await polykeyAgent.gestaltGraph.getGestaltActionsByIdentity(

      at Object.<anonymous> (tests/bin/identities/identities.test.ts:189:31)

  ● CLI Identities › commandAllowGestalts › Should fail on invalid inputs.

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      223 |       expect(result3.exitCode).toBe(1); // Should fail.
      224 |     });
    > 225 |     test('Should fail on invalid inputs.', async () => {
          |     ^
      226 |       let result;
      227 |       // Invalid node.
      228 |       result = await testBinUtils.pkStdio(

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/bin/identities/identities.test.ts:225:5)

  ● CLI Identities › commandDisallowGestalts › Should disallow permissions on Node.

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      258 |   });
      259 |   describe('commandDisallowGestalts', () => {
    > 260 |     test('Should disallow permissions on Node.', async () => {
          |     ^
      261 |       // Setting permissions.
      262 |       await polykeyAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'notify');
      263 |       await polykeyAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'scan');

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/bin/identities/identities.test.ts:260:5)

  ● CLI Identities › commandDisallowGestalts › Should disallow permissions on Identity.

    expect(received).toBe(expected) // Object.is equality

    Expected: 0
    Received: 255

      293 |       ]);
      294 |       const result = await testBinUtils.pkStdio(commands, {}, dataDir);
    > 295 |       expect(result.exitCode).toBe(0); // Succeeds.
          |                               ^
      296 |
      297 |       const actions =
      298 |         await polykeyAgent.gestaltGraph.getGestaltActionsByIdentity(

      at Object.<anonymous> (tests/bin/identities/identities.test.ts:295:31)

  ● CLI Identities › commandDisallowGestalts › Should fail on invalid inputs.

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      304 |       expect(actionKeys).not.toContain('scan');
      305 |     });
    > 306 |     test('Should fail on invalid inputs.', async () => {
          |     ^
      307 |       let result;
      308 |       // Invalid node.
      309 |       result = await testBinUtils.pkStdio(

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/bin/identities/identities.test.ts:306:5)

  ● CLI Identities › commandPermissionsGestalts › Should get permissions on Node.

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      339 |   });
      340 |   describe('commandPermissionsGestalts', () => {
    > 341 |     test('Should get permissions on Node.', async () => {
          |     ^
      342 |       // Setting permissions.
      343 |       await polykeyAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'notify');
      344 |       await polykeyAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'scan');

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/bin/identities/identities.test.ts:341:5)

  ● CLI Identities › commandPermissionsGestalts › Should get permissions on Identity.

    expect(received).toBe(expected) // Object.is equality

    Expected: 0
    Received: 255

      369 |       ]);
      370 |       const result = await testBinUtils.pkStdio(commands, {}, dataDir);
    > 371 |       expect(result.exitCode).toBe(0); // Succeeds.
          |                               ^
      372 |       // Print result.
      373 |       expect(result.stdout).toContain('scan');
      374 |       expect(result.stdout).toContain('notify');

      at Object.<anonymous> (tests/bin/identities/identities.test.ts:371:31)

  ● CLI Identities › commandTrustGestalts › Should set trust on Node.

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      376 |   });
      377 |   describe('commandTrustGestalts', () => {
    > 378 |     test('Should set trust on Node.', async () => {
          |     ^
      379 |       const commands = genCommands(['trust', node1.id]);
      380 |       const result = await testBinUtils.pkStdio(commands, {}, dataDir);
      381 |       expect(result.exitCode).toBe(0); // Succeeds.

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/bin/identities/identities.test.ts:378:5)

  ● CLI Identities › commandTrustGestalts › Should set trust on Identity.

    expect(received).toBe(expected) // Object.is equality

    Expected: 0
    Received: 255

      393 |       ]);
      394 |       const result = await testBinUtils.pkStdio(commands, {}, dataDir);
    > 395 |       expect(result.exitCode).toBe(0); // Succeeds.
          |                               ^
      396 |
      397 |       const actions =
      398 |         await polykeyAgent.gestaltGraph.getGestaltActionsByIdentity(

      at Object.<anonymous> (tests/bin/identities/identities.test.ts:395:31)

  ● CLI Identities › commandTrustGestalts › Should fail on invalid inputs.

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      403 |       expect(actionKeys).toContain('notify');
      404 |     });
    > 405 |     test('Should fail on invalid inputs.', async () => {
          |     ^
      406 |       let result;
      407 |       // Invalid node.
      408 |       result = await testBinUtils.pkStdio(

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/bin/identities/identities.test.ts:405:5)

  ● CLI Identities › commandUntrustGestalts › Should unset trust on Node.

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      429 |   });
      430 |   describe('commandUntrustGestalts', () => {
    > 431 |     test('Should unset trust on Node.', async () => {
          |     ^
      432 |       // Setting permissions.
      433 |       await polykeyAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'notify');
      434 |       await polykeyAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'scan');

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/bin/identities/identities.test.ts:431:5)

  ● CLI Identities › commandUntrustGestalts › Should unset trust on Identity.

    expect(received).toBe(expected) // Object.is equality

    Expected: 0
    Received: 255

      463 |       ]);
      464 |       const result = await testBinUtils.pkStdio(commands, {}, dataDir);
    > 465 |       expect(result.exitCode).toBe(0); // Succeeds.
          |                               ^
      466 |
      467 |       const actions =
      468 |         await polykeyAgent.gestaltGraph.getGestaltActionsByIdentity(

      at Object.<anonymous> (tests/bin/identities/identities.test.ts:465:31)

  ● CLI Identities › commandUntrustGestalts › Should fail on invalid inputs.

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      474 |       expect(actionKeys).not.toContain('notify');
      475 |     });
    > 476 |     test('Should fail on invalid inputs.', async () => {
          |     ^
      477 |       let result;
      478 |       // Invalid node.
      479 |       result = await testBinUtils.pkStdio(

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/bin/identities/identities.test.ts:476:5)

  ● CLI Identities › commandGetGestalts › Should list gestalt by Node

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      562 |   describe('commandGetGestalts', () => {
      563 |     const nodeIdEncoded = node1.id;
    > 564 |     test('Should list gestalt by Node', async () => {
          |     ^
      565 |       const commands = ['identities', 'get', '-np', nodePath, nodeIdEncoded];
      566 |       const result = await testBinUtils.pkStdio(commands, {}, dataDir);
      567 |       expect(result.exitCode).toBe(0);

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/bin/identities/identities.test.ts:564:5)

  ● CLI Identities › commandGetGestalts › Should list gestalt by Identity

    expect(received).toBe(expected) // Object.is equality

    Expected: 0
    Received: 255

      580 |       ];
      581 |       const result = await testBinUtils.pkStdio(commands, {}, dataDir);
    > 582 |       expect(result.exitCode).toBe(0);
          |                               ^
      583 |       expect(result.stdout).toContain(nodeIdEncoded);
      584 |       expect(result.stdout).toContain(identity1.providerId);
      585 |       expect(result.stdout).toContain(identity1.identityId);

      at Object.<anonymous> (tests/bin/identities/identities.test.ts:582:31)

  ● CLI Identities › commandDiscoverGestalts › Should start discovery by Node

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      726 |       await nodeC.gestaltGraph.clearDB();
      727 |     });
    > 728 |     test('Should start discovery by Node', async () => {
          |     ^
      729 |       // Authenticate identity
      730 |       await polykeyAgent.identitiesManager.putToken(
      731 |         testToken.providerId,

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/bin/identities/identities.test.ts:728:5)

  ● CLI Identities › commandDiscoverGestalts › Should start discovery by Identity

    expect(received).toBe(expected) // Object.is equality

    Expected: 0
    Received: 255

      777 |       ];
      778 |       const result = await testBinUtils.pkStdio(commands, {}, dataDir);
    > 779 |       expect(result.exitCode).toBe(0);
          |                               ^
      780 |       // We expect to find a gestalt now.
      781 |       const gestalt = await polykeyAgent.gestaltGraph.getGestalts();
      782 |       expect(gestalt.length).not.toBe(0);

      at Object.<anonymous> (tests/bin/identities/identities.test.ts:779:31)

Test Suites: 1 failed, 1 total
Tests:       18 failed, 5 passed, 23 total
Snapshots:   0 total
Time:        235.437 s
Ran all test suites matching /tests\/bin\/identities\//i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-sqdL5Y
npm ERR! Test failed.  See above for more details.
keys
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/bin/keys

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/bin/keys"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-e0Q80g
 PASS  tests/bin/keys/renew.test.ts (46.11 s)
  renew
    ✓ renews the keypair (4365 ms)

 PASS  tests/bin/keys/reset.test.ts (46.65 s)
  renew
    ✓ resets the keypair (3912 ms)

 PASS  tests/bin/keys/decrypt.test.ts (53.746 s)
  decrypt
    ✓ decrypts data (1129 ms)

 PASS  tests/bin/keys/certchain.test.ts (8.365 s)
  certchain
    ✓ certchain gets the certificate chain (1123 ms)

 PASS  tests/bin/keys/password.test.ts (56.985 s)
  password
    ✓ password changes the root password (1974 ms)

 PASS  tests/bin/keys/cert.test.ts
  cert
    ✓ cert gets the certificate (507 ms)

 PASS  tests/bin/keys/sign.test.ts (11.971 s)
  sign
    ✓ signs a file (552 ms)

 PASS  tests/bin/keys/encrypt.test.ts (58.741 s)
  encrypt
    ✓ encrypts data (539 ms)

 PASS  tests/bin/keys/verify.test.ts (60.125 s)
  verify
    ✓ verifies a file (1079 ms)

 PASS  tests/bin/keys/root.test.ts (61.862 s)
  root
    ✓ root gets the public key (550 ms)
    ✓ root gets the keypair (491 ms)

Test Suites: 10 passed, 10 total
Tests:       11 passed, 11 total
Snapshots:   0 total
Time:        62.315 s
Ran all test suites matching /tests\/bin\/keys/i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-e0Q80g

nodes
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/bin/nodes

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/bin/nodes"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-jrgTB1
 FAIL  tests/bin/nodes/add.test.ts (33.234 s)
  add
    ✕ add a node (14 ms)
    ✕ fail to add a node (invalid node ID) (6 ms)
    ✕ fail to add a node (invalid IP address) (5 ms)

  ● add › add a node

    expect(received).toBe(expected) // Object.is equality

    Expected: 0
    Received: 64

      75 |     ]);
      76 |     const result = await testBinUtils.pkStdio(commands, {}, dataDir);
    > 77 |     expect(result.exitCode).toBe(0);
         |                             ^
      78 |
      79 |     // Checking if node was added.
      80 |     const res = await polykeyAgent.nodeManager.getNode(validNodeId);

      at Object.<anonymous> (tests/bin/nodes/add.test.ts:77:29)

  ● add › fail to add a node (invalid node ID)

    expect(received).toContain(expected) // indexOf

    Expected substring: "Invalid node ID."
    Received string:    "error: command-argument value 'v9575cgac9524ih0' is invalid for argument 'nodeId'. Node ID must be multibase base32hex encoded public-keys·
    Usage: polykey nodes add [options] <nodeId> <host> <port>·
    Add a Node to the Node Graph·
    Arguments:
      nodeId                       Id of the node to add
      host                         Address of the node
      port                         Port of the node·
    Options:
      -np, --node-path <path>      Path to Node State (default:
                                   \"/home/josh/.local/share/polykey\", env:
                                   PK_NODE_PATH)
      -pf, --password-file <path>  Path to Password
      -f, --format <format>        Output Format (choices: \"human\", \"json\",
                                   default: \"human\")
      -v, --verbose                Log Verbose Messages (default: 0)
      -ni, --node-id <id>           (env: PK_NODE_ID)
      -ch, --client-host <host>    Client Host Address (env: PK_CLIENT_HOST)
      -cp, --client-port <port>    Client Port (env: PK_CLIENT_PORT)
      -h, --help                   display help for command
    "

      94 |       const result = await testBinUtils.pkStdio(commands, {}, dataDir);
      95 |       expect(result.exitCode).not.toBe(0);
    > 96 |       expect(result.stderr).toContain('Invalid node ID.');
         |                             ^
      97 |     },
      98 |     global.failedConnectionTimeout,
      99 |   );

      at Object.<anonymous> (tests/bin/nodes/add.test.ts:96:29)

  ● add › fail to add a node (invalid IP address)

    expect(received).toContain(expected) // indexOf

    Expected substring: "Invalid IP address."
    Received string:    "error: command-argument value 'INVALIDHOST' is invalid for argument 'host'. Host must be an IPv4 or IPv6 address·
    Usage: polykey nodes add [options] <nodeId> <host> <port>·
    Add a Node to the Node Graph·
    Arguments:
      nodeId                       Id of the node to add
      host                         Address of the node
      port                         Port of the node·
    Options:
      -np, --node-path <path>      Path to Node State (default:
                                   \"/home/josh/.local/share/polykey\", env:
                                   PK_NODE_PATH)
      -pf, --password-file <path>  Path to Password
      -f, --format <format>        Output Format (choices: \"human\", \"json\",
                                   default: \"human\")
      -v, --verbose                Log Verbose Messages (default: 0)
      -ni, --node-id <id>           (env: PK_NODE_ID)
      -ch, --client-host <host>    Client Host Address (env: PK_CLIENT_HOST)
      -cp, --client-port <port>    Client Port (env: PK_CLIENT_PORT)
      -h, --help                   display help for command
    "

      109 |       const result = await testBinUtils.pkStdio(commands, {}, dataDir);
      110 |       expect(result.exitCode).not.toBe(0);
    > 111 |       expect(result.stderr).toContain('Invalid IP address.');
          |                             ^
      112 |
      113 |       // Checking if node was added.
      114 |       const res = await polykeyAgent.nodeManager.getNode(validNodeId);

      at Object.<anonymous> (tests/bin/nodes/add.test.ts:111:29)

WARN:ConnectionForward 0.0.0.0:40253:Forward Error: ErrorConnectionEndTimeout
 FAIL  tests/bin/nodes/claim.test.ts (35.35 s)
  claim
    ✕ send a gestalt invite (486 ms)
    ✕ send a gestalt invite (force invite) (228 ms)
    ✕ claim the remote node (186 ms)

  ● claim › send a gestalt invite

    expect(received).toContain(expected) // indexOf

    Expected substring: "vq4p3khhj5tqj4834gved3ro271eq4u51jdu288839lcbuh1sokkg"
    Received string:    "Successfully sent Gestalt Invite notification to Keynode with ID Ñ2:F3/u2 d�ÜÑï�8]¢x¡�|$!�MX¿D<Å)
    "

      117 |       expect(result.exitCode).toBe(0); // Succeeds.
      118 |       expect(result.stdout).toContain('Gestalt Invite');
    > 119 |       expect(result.stdout).toContain(remoteOnlineNodeIdEncoded);
          |                             ^
      120 |     },
      121 |     global.polykeyStartupTimeout * 4,
      122 |   );

      at Object.<anonymous> (tests/bin/nodes/claim.test.ts:119:29)

  ● claim › send a gestalt invite (force invite)

    expect(received).toContain(expected) // indexOf

    Expected substring: "vq4p3khhj5tqj4834gved3ro271eq4u51jdu288839lcbuh1sokkg"
    Received string:    "Successfully sent Gestalt Invite notification to Keynode with ID Ñ2:F3/u2 d�ÜÑï�8]¢x¡�|$!�MX¿D<Å)
    "

      134 |     expect(result.exitCode).toBe(0); // Succeeds.
      135 |     expect(result.stdout).toContain('Gestalt Invite');
    > 136 |     expect(result.stdout).toContain(remoteOnlineNodeIdEncoded);
          |                           ^
      137 |   });
      138 |   test('claim the remote node', async () => {
      139 |     await remoteOnline.notificationsManager.sendNotification(keynodeId, {

      at Object.<anonymous> (tests/bin/nodes/claim.test.ts:136:27)

  ● claim › claim the remote node

    expect(received).toContain(expected) // indexOf

    Expected substring: "vq4p3khhj5tqj4834gved3ro271eq4u51jdu288839lcbuh1sokkg"
    Received string:    "Successfully generated a cryptolink claim on Keynode with ID Ñ2:F3/u2 d�ÜÑï�8]¢x¡�|$!�MX¿D<Å)
    "

      148 |     expect(result.exitCode).toBe(0); // Succeeds.
      149 |     expect(result.stdout).toContain('cryptolink claim');
    > 150 |     expect(result.stdout).toContain(remoteOnlineNodeIdEncoded);
          |                           ^
      151 |   });
      152 | });
      153 |

      at Object.<anonymous> (tests/bin/nodes/claim.test.ts:150:27)

ERROR:ForwardProxy:Failed CONNECT to 0.0.0.0:36651 - ErrorConnectionStartTimeout
  console.error
    Failed to connect to 0.0.0.0:36651/?nodeId=vl9kt886n67122kh8vl61n40ronajptui2die1pak4soe7forusc0 through proxy 127.0.0.1:37943 with status 504

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

 FAIL  tests/bin/nodes/find.test.ts (154.296 s)
  find
    ✓ find an online node (76 ms)
    ✓ find an offline node (77 ms)
    ✕ fail to find an unknown node (100006 ms)

  ● find › fail to find an unknown node

    : Timeout - Async callback was not invoked within the 100000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 100000 ms timeout specified by jest.setTimeout.Error:

      173 |     );
      174 |   });
    > 175 |   test(
          |   ^
      176 |     'fail to find an unknown node',
      177 |     async () => {
      178 |       const unknownNodeId = nodesUtils.decodeNodeId(

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/bin/nodes/find.test.ts:175:3)

WARN:ConnectionForward 0.0.0.0:45621:Client Error: ErrorConnectionEndTimeout
 FAIL  tests/bin/nodes/ping.test.ts (197.471 s)
  ping
    ✓ fail when pinging an offline node (40096 ms)
    ✕ fail if node cannot be found (100008 ms)
    ✓ succeed when pinging a live node (62 ms)

  ● ping › fail if node cannot be found

    : Timeout - Async callback was not invoked within the 100000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 100000 ms timeout specified by jest.setTimeout.Error:

      120 |     global.failedConnectionTimeout * 2,
      121 |   );
    > 122 |   test(
          |   ^
      123 |     'fail if node cannot be found',
      124 |     async () => {
      125 |       const fakeNodeId = nodesUtils.decodeNodeId(

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/bin/nodes/ping.test.ts:122:3)

A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks.
Test Suites: 4 failed, 4 total
Tests:       8 failed, 4 passed, 12 total
Snapshots:   0 total
Time:        198.471 s
Ran all test suites matching /tests\/bin\/nodes/i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-jrgTB1
npm ERR! Test failed.  See above for more details.

notifications
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/bin/notifications/

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/bin/notifications/"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-evSwRY
WARN:ConnectionForward 0.0.0.0:50802:Client Error: ErrorConnectionEndTimeout
 PASS  tests/bin/notifications/notifications.test.ts (18.207 s)
  CLI Notifications
    commandSendNotification
      ✓ Should send notification with permission. (321 ms)
      ✓ Should send notification without permission. (88 ms)
    commandReadNotifications
      ✓ Should read all notifications. (290 ms)
      ✓ Should read all unread notifications. (248 ms)
      ✓ Should read a fixed number of notifications. (194 ms)
      ✓ Should read notifications in reverse order. (232 ms)
      ✓ Should read no notifications. (21 ms)
      ✓ Should read all types of notifications. (140 ms)
    commandClearNotifications
      ✓ Should remove all notifications. (220 ms)

Test Suites: 1 passed, 1 total
Tests:       9 passed, 9 total
Snapshots:   0 total
Time:        18.246 s
Ran all test suites matching /tests\/bin\/notifications\//i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-evSwRY
secrets
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/bin/secrets/

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/bin/secrets/"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-EkA2WQ
 PASS  tests/bin/secrets/secrets.test.ts (40.509 s)
  CLI secrets
    commandCreateSecret
      ✓ should create secrets (2289 ms)
    commandDeleteSecret
      ✓ should delete secrets (2695 ms)
    commandGetSecret
      ✓ should retrieve secrets (1766 ms)
    commandListSecrets
      ✓ should list secrets (5916 ms)
    commandNewDir
      ✓ should make a directory (5008 ms)
    commandRenameSecret
      ✓ should rename secrets (3111 ms)
    commandUpdateSecret
      ✓ should update secrets (2716 ms)
    commandNewDirSecret
      ✓ should add a directory of secrets (2812 ms)

Test Suites: 1 passed, 1 total
Tests:       8 passed, 8 total
Snapshots:   0 total
Time:        40.553 s
Ran all test suites matching /tests\/bin\/secrets\//i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-EkA2WQ

vaults
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/bin/vaults

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/bin/vaults"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-1bEfmu
 PASS  tests/bin/vaults/vaults.test.ts (42.161 s)
  CLI vaults
    commandListVaults
      ✓ should list all vaults (1587 ms)
    commandCreateVaults
      ✓ should create vaults (1412 ms)
    commandRenameVault
      ✓ should rename vault (690 ms)
      ✓ should fail to rename non-existent vault (706 ms)
    commandDeleteVault
      ✓ should delete vault (949 ms)
    commandVaultStats
      ○ skipped should return the stats of a vault
    commandSetPermsVault
      ○ skipped should share a vault
    commandUnsetPermsVault
      ○ skipped should un-share a vault
    commandVaultPermissions
      ○ skipped should get permissions of a vault
    commandPullVault
      ○ skipped should clone a vault
      ○ skipped should pull a vault
    commandScanVault
      ○ skipped should scan a node for vaults
    commandVaultVersion
      ✓ should switch the version of a vault (3022 ms)
      ✓ should switch the version of a vault to the latest version (3453 ms)
      ✓ should handle invalid version IDs (813 ms)
      ✓ should throw an error if the vault is not found (22 ms)
    commandVaultLog
      ✓ Should get all commits (5216 ms)
      ✓ should get a part of the log (5045 ms)
      ✓ should get a specific commit (4974 ms)
      ✎ todo test formatting of the output

Test Suites: 1 passed, 1 total
Tests:       7 skipped, 1 todo, 12 passed, 20 total
Snapshots:   0 total
Time:        42.199 s
Ran all test suites matching /tests\/bin\/vaults/i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-1bEfmu
standalone tests not in directory
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/bin/polykey.test.ts tests/bin/bootstrap.test.ts tests/bin/sessions.test.ts tests/bin/utils.test.ts  tests/bin/utils.retryAuthentication.test.ts 

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/bin/polykey.test.ts" "tests/bin/bootstrap.test.ts" "tests/bin/sessions.test.ts" "tests/bin/utils.test.ts" "tests/bin/utils.retryAuthentication.test.ts"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-0u5Od0
 PASS  tests/bin/utils.test.ts (23.936 s)
  bin/utils
    ✓ list in human and json format (5 ms)
    ✓ table in human and in json format (1 ms)
    ✓ dict in human and in json format (2 ms)

 PASS  tests/bin/polykey.test.ts (27.087 s)
  polykey
    ✓ default help display (58 ms)

 PASS  tests/bin/utils.retryAuthentication.test.ts (28.897 s)
  bin/utils retryAuthentication
    ✓ no retry on success (6 ms)
    ✓ no retry on generic error (3 ms)
    ✓ no retry on unattended call with PK_TOKEN and PK_PASSWORD (3 ms)
    ✓ no retry on unattended call with PK_TOKEN (2 ms)
    ✓ no retry on unattended call with PK_PASSWORD (2 ms)
    ✓ retry once on clientErrors.ErrorClientAuthMissing (4 ms)
    ✓ retry 2 times on clientErrors.ErrorClientAuthDenied (3 ms)
    ✓ retry 2+ times on clientErrors.ErrorClientAuthDenied until generic error (3 ms)

 PASS  tests/bin/sessions.test.ts (46.908 s)
  sessions
    ✓ serial commands refresh the session token (2388 ms)
    ✓ unattended commands with invalid authentication should fail (1160 ms)
    ✓ prompt for password to authenticate attended commands (587 ms)
    ✓ re-prompts for password if unable to authenticate command (1174 ms)

 PASS  tests/bin/bootstrap.test.ts (62.788 s)
  bootstrap
    ✓ bootstraps node state (8142 ms)
    ✓ bootstrapping occupied node state (12768 ms)
    ✓ concurrent bootstrapping results in 1 success (4144 ms)
    ✓ bootstrap when interrupted, requires fresh on next bootstrap (10062 ms)

Test Suites: 5 passed, 5 total
Tests:       20 passed, 20 total
Snapshots:   0 total
Time:        63.178 s
Ran all test suites matching /tests\/bin\/polykey.test.ts|tests\/bin\/bootstrap.test.ts|tests\/bin\/sessions.test.ts|tests\/bin\/utils.test.ts|tests\/bin\/utils.retryAuthentication.test.ts/i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-0u5Od0

  • - bootstrap
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/bootstrap/

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/bootstrap/"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-AdpEgX
 PASS  tests/bootstrap/utils.test.ts (12.324 s)
  bootstrap/utils
    ✓ bootstraps new node path (1111 ms)
    ✓ bootstraps existing but empty node path (1082 ms)
    ✓ bootstrap fails if non-empty node path (14 ms)
    ✓ concurrent bootstrapping results in 1 success (992 ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        12.362 s
Ran all test suites matching /tests\/bootstrap\//i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-AdpEgX

  • - claims
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/claims/

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/claims/"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-5kXokz
 PASS  tests/claims/utils.test.ts (11.041 s)
  claims/utils
    ✓ creates a claim (both node and identity) (16 ms)
    ✓ decodes a singly signed node claim (6 ms)
    ✓ decodes a doubly signed node claim (11 ms)
    ✓ decodes an identity claim (7 ms)
    ✓ fails to decode an invalid claim (12 ms)
    ✓ decodes a claim header (7 ms)
    ✓ re-encodes a claim (103 ms)
    ✓ verifies a claim signature (189 ms)
    ✓ verifies a claim hash (1035 ms)
    ✓ validates valid claims (2 ms)
    ✓ rejects invalid singly signed claims (10 ms)
    ✓ rejects invalid doubly signed claims (5 ms)
    ✓ rejects invalid identity claims (11 ms)

Test Suites: 1 passed, 1 total
Tests:       13 passed, 13 total
Snapshots:   0 total
Time:        11.078 s
Ran all test suites matching /tests\/claims\//i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-5kXokz

  • - client
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/client/service/

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/client/service/"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-nrp2yp
 PASS  tests/client/service/notificationsClear.test.ts (42.264 s)
  notificationsClear
    ✓ clears notifications (1783 ms)

 PASS  tests/client/service/notificationsSend.test.ts (44.287 s)
  notificationsSend
    ✓ sends a notification (3164 ms)

 PASS  tests/client/service/identitiesInfoGetConnected.test.ts
  identitiesInfoGetConnected
    ✓ gets connected identities (112 ms)

 PASS  tests/client/service/identitiesClaim.test.ts (46.209 s)
  identitiesClaim
    ✓ claims identity (3733 ms)

 PASS  tests/client/service/keysKeyPairReset.test.ts (46.539 s)
  keysKeyPairReset
    ✓ resets the root key pair (5659 ms)

 PASS  tests/client/service/keysKeyPairRenew.test.ts (49.91 s)
  keysKeyPairRenew
    ✓ renews the root key pair (8404 ms)

 PASS  tests/client/service/nodesClaim.test.ts (51.994 s)
  nodesClaim
    ✓ sends a gestalt invite (none existing) (3405 ms)
    ✓ sends a gestalt invite (force invite) (3336 ms)
    ✓ claims a node (3859 ms)

 PASS  tests/client/service/nodesPing.test.ts (9.988 s)
  nodesPing
    ✓ pings a node (offline) (4000 ms)
    ✓ pings a node (online) (3627 ms)

 PASS  tests/client/service/identitiesAuthenticate.test.ts
  identitiesAuthenticate
    ✓ authenticates identity (178 ms)

 PASS  tests/client/service/nodesAdd.test.ts (7.123 s)
  nodesAdd
    ✓ adds a node (5254 ms)

 PASS  tests/client/service/nodesFind.test.ts (7.339 s)
  nodesFind
    ✓ finds a node (4322 ms)

 PASS  tests/client/service/agentStatus.test.ts (7.464 s)
  agentStatus
    ✓ gets status (4700 ms)

 PASS  tests/client/service/identitiesInfoGet.test.ts
  identitiesInfoGet
    ✓ gets identity (120 ms)

 PASS  tests/client/service/identitiesTokenPut.test.ts
  identitiesTokenPut
    ✓ authenticates providers (92 ms)

 PASS  tests/client/service/identitiesTokenGet.test.ts
  identitiesTokenGet
    ✓ gets provider tokens (97 ms)

 PASS  tests/client/service/identitiesTokenDelete.test.ts
  identitiesTokenDelete
    ✓ un-authenticates providers (61 ms)

 PASS  tests/client/service/identitiesProvidersList.test.ts
  identitiesProvidersList
    ✓ lists providers (25 ms)

 PASS  tests/client/service/agentLockAll.test.ts (6.738 s)
  agentLockall
    ✓ locks all sessions (4673 ms)

 PASS  tests/client/service/agentStop.test.ts (7.377 s)
  agentStop
    ✓ stops the agent (4483 ms)

 PASS  tests/client/service/keysCertsChainGet.test.ts (6.034 s)
  keysCertsChainGet
    ✓ gets the root certchain (4119 ms)

 PASS  tests/client/service/keysPasswordChange.test.ts (5.7 s)
  keysPasswordChange
    ✓ changes the password (3476 ms)

 PASS  tests/client/service/keysCertsGet.test.ts (5.02 s)
  keysCertsGet
    ✓ gets the root certificate (3461 ms)

 PASS  tests/client/service/notificationsRead.test.ts (62.45 s)
  notificationsRead
    ✓ reads a single notification (2686 ms)
    ✓ reads unread notifications (3543 ms)
    ✓ reads notifications in reverse order (4136 ms)
    ✓ reads gestalt invite notifications (4250 ms)
    ✓ reads vault share notifications (3485 ms)
    ✓ reads no notifications (3174 ms)

 PASS  tests/client/service/agentUnlock.test.ts
  agentUnlock
    ✓ requests a session (13 ms)

 PASS  tests/client/service/keysVerify.test.ts (5.107 s)
  keysVerify
    ✓ verifies signatures (3749 ms)

 PASS  tests/client/service/keysKeyPairRoot.test.ts
  keysKeyPairRoot
    ✓ gets the root keypair (3072 ms)

 PASS  tests/client/service/keysEncrypt.test.ts
  keysEncrypt
    ✓ encrypts data (3167 ms)

 PASS  tests/client/service/keysSign.test.ts
  keysSign
    ✓ signs with root keypair (3120 ms)

 PASS  tests/client/service/keysDecrypt.test.ts
  keysDecrypt
    ✓ decrypts data (2225 ms)

Test Suites: 29 passed, 29 total
Tests:       37 passed, 37 total
Snapshots:   0 total
Time:        64.848 s
Ran all test suites matching /tests\/client\/service\//i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-nrp2yp


[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/client/GRPCClientClient.
test.ts tests/client/rpc*              

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/client/GRPCClientClient.test.ts" "tests/client/rpcGestalts.test.ts" "tests/client/rpcVaults.test.ts"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-0tNhvL
 PASS  tests/client/rpcGestalts.test.ts (21.26 s)
  Client service
    ✓ should get all gestalts (103 ms)
    ✓ should set independent node and identity gestalts (14 ms)
    ✓ should get gestalt from Node. (33 ms)
    ✓ should get gestalt from identity. (26 ms)
    ✓ should discover gestalt via Node. (70 ms)
    ✓ should discover gestalt via Identity. (16 ms)
    ✓ should get gestalt permissions by node. (27 ms)
    ✓ should get gestalt permissions by Identity. (38 ms)
    ✓ should set gestalt permissions by node. (37 ms)
    ✓ should set gestalt permissions by Identity. (36 ms)
    ✓ should unset gestalt permissions by node. (32 ms)
    ✓ should unset gestalt permissions by Identity. (52 ms)

 PASS  tests/client/GRPCClientClient.test.ts (22.652 s)
  GRPCClientClient
    ✓ cannot be called when destroyed (60 ms)
    ✓ can get status (802 ms)

 PASS  tests/client/rpcVaults.test.ts (26.362 s)
  Vaults client service
    Vaults
      ○ skipped should get vaults
      ○ skipped should create vault
      ○ skipped should delete vaults
      ○ skipped should rename vaults
      Version
        ○ skipped should switch a vault to a version
        ○ skipped should fail to find a non existent version
      Vault Log
        ○ skipped should get the full log
        ○ skipped should get a part of the log
        ○ skipped should get a specific commit
    Secrets
      ✓ should add a directory of secrets in a vault (5627 ms)
      ○ skipped should make a directory in a vault
      ○ skipped should list secrets in a vault
      ○ skipped should delete secrets in a vault
      ○ skipped should edit secrets in a vault
      ○ skipped should get secrets in a vault
      ○ skipped should rename secrets in a vault
      ○ skipped should add secrets in a vault

Test Suites: 3 passed, 3 total
Tests:       16 skipped, 15 passed, 31 total
Snapshots:   0 total
Time:        26.713 s
Ran all test suites matching /tests\/client\/GRPCClientClient.test.ts|tests\/client\/rpcGestalts.test.ts|tests\/client\/rpcVaults.test.ts/i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-0tNhvL

  • - discovery
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/discovery/

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/discovery/"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-NMNYLv
WARN:ConnectionForward 0.0.0.0:33170:Forward Error: ErrorConnectionEndTimeout
 PASS  tests/discovery/Discovery.test.ts (36.13 s)
  Discovery
    ✓ discovery readiness (10 ms)
    ✓ discovery by node (694 ms)
    ✓ discovery by identity (154 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        36.168 s
Ran all test suites matching /tests\/discovery\//i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-NMNYLv

  • - events
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/events

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/events"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-dX7Cep
 PASS  tests/events/EventBus.test.ts
  EventBus
    ✓ synchronous handlers simple (3 ms)
    ✓ synchronous handlers complex (1 ms)
    ✓ asynchronous handlers simple (1 ms)
    ✓ asynchronous handlers complex (2 ms)
    ✓ mixed handlers simple (10 ms)
    ✓ mixed handlers complex (32 ms)
    ✓ error handling (2 ms)
    ✓ error handling and catching (1 ms)
    ✓ error handling using symbol (1 ms)

Test Suites: 1 passed, 1 total
Tests:       9 passed, 9 total
Snapshots:   0 total
Time:        0.876 s
Ran all test suites matching /tests\/events/i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-dX7Cep

  • - gestalts
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/gestalts

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/gestalts"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-8lYcb9
 PASS  tests/gestalts/GestaltGraph.test.ts (9.536 s)
  GestaltGraph
    ✓ gestaltGraph readiness (44 ms)
    ✓ get, set and unset node (22 ms)
    ✓ get, set and unset identity (9 ms)
    ✓ setting independent node and identity gestalts (14 ms)
    ✓ start and stop preserves state (10 ms)
    ✓ link node to node (14 ms)
    ✓ link node to identity (11 ms)
    ✓ link node to node and identity (20 ms)
    ✓ getting all gestalts (23 ms)
    ✓ new node gestalts creates a new acl record (9 ms)
    ✓ new identity gestalts does not create a new acl record (5 ms)
    ✓ set and unset gestalt actions (12 ms)
    ✓ linking 2 new nodes results in a merged permission (14 ms)
    ✓ linking 2 existing nodes results in a merged permission (21 ms)
    ✓ link existing node to new node (23 ms)
    ✓ linking new node and new identity results in a merged permission (14 ms)
    ✓ linking existing node and existing identity results in merged permission (35 ms)
    ✓ link existing node to new identity (18 ms)
    ✓ link new node to existing identity (16 ms)
    ✓ splitting node and node results in split inherited permissions (27 ms)
    ✓ splitting node and identity results in split inherited permissions unless the identity is a loner (18 ms)
    ✓ removing a gestalt removes the permission (19 ms)

Test Suites: 1 passed, 1 total
Tests:       22 passed, 22 total
Snapshots:   0 total
Time:        9.57 s
Ran all test suites matching /tests\/gestalts/i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-8lYcb9
  • - git
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/git

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/git"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-8i2MbX
 PASS  tests/git/utils.test.ts (10.87 s)
  Git utils
    read index
      ✓ of a packfile (36 ms)
    list refs
      ✓ on master (54 ms)
    encoding
      ✓ a string
      ✓ an empty string
      ✓ an upload pack (225 ms)
    resolve refs
      ✓ to a commit oid (1 ms)
      ✓ to HEAD (67 ms)
      ✓ to HEAD including depth (31 ms)
      ✓ to non-existant refs (46 ms)
    read an object
      ✓ missing (47 ms)
      ✓ parsed (22 ms)
      ✓ content (24 ms)
      ✓ wrapped (24 ms)
      ✓ deflated (25 ms)
      ✓ from packfile (123 ms)

Test Suites: 1 passed, 1 total
Tests:       15 passed, 15 total
Snapshots:   0 total

  • - grpc
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/grpc

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/grpc"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-ey3Ziw
 PASS  tests/grpc/utils.test.ts (18.113 s)
  GRPC utils
    ✓ promisified client unary call (38 ms)
    ✓ promisified client unary call error (97 ms)
    ✓ promisified reading server stream (63 ms)
    ✓ promisified reading server stream - error (11 ms)
    ✓ promisified reading server stream - destroy first (2 ms)
    ✓ promisified reading server stream - destroy second (10 ms)
    ✓ promisified writing client stream (18 ms)
    ✓ promisified writing client stream - end first (4 ms)
    ✓ promisify reading and writing duplex stream (6 ms)
    ✓ promisify reading and writing duplex stream - end and destroy with next (2 ms)
    ✓ promisify reading and writing duplex stream - end and destroy with write and read (1 ms)
    ✓ promisify reading and writing duplex stream - cannot write after destroy (18 ms)
    ✓ promisify reading and writing duplex stream - error with read (24 ms)
    ✓ promisify reading and writing duplex stream - error with next (4 ms)

 PASS  tests/grpc/GRPCClient.test.ts (25.672 s)
  GRPCClient
    ✓ starting and stopping the client (36 ms)
    ✓ calling unary (39 ms)
    ✓ calling unary with session interception (74 ms)
    ✓ calling server stream (49 ms)
    ✓ calling server stream with session interception (24 ms)
    ✓ calling client stream (23 ms)
    ✓ calling client stream with session interception (21 ms)
    ✓ calling duplex stream (22 ms)
    ✓ calling duplex stream with session interception (20 ms)

 PASS  tests/grpc/GRPCServer.test.ts (28.31 s)
  GRPCServer
    ✓ GRPCServer readiness (9 ms)
    ✓ starting and stopping the server (568 ms)
    ✓ connecting to the server securely (1021 ms)
    ✓ changing the private key and certificate on the fly (1465 ms)
    ✓ authenticated commands acquire a token (1438 ms)

Test Suites: 3 passed, 3 total
Tests:       28 passed, 28 total
Snapshots:   0 total
Time:        28.66 s
Ran all test suites matching /tests\/grpc/i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-ey3Ziw

  • - http
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/http

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/http"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-W0B3eO
 PASS  tests/http/utils.test.ts
  Http utils
    ✓ termination of http server (12 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.735 s
Ran all test suites matching /tests\/http/i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-W0B3eO

  • - identities
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/identities/

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/identities/"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-lz5iDI
 PASS  tests/identities/IdentitiesManager.test.ts (8.983 s)
  IdentitiesManager
    ✓ IdentitiesManager readiness (32 ms)
    ✓ get, set and unset tokens (19 ms)
    ✓ start and stop preserves state (8 ms)
    ✓ fresh start deletes all state (6 ms)
    ✓ register and unregister providers (9 ms)
    ✓ using TestProvider (19 ms)

Test Suites: 1 passed, 1 total
Tests:       6 passed, 6 total
Snapshots:   0 total
Time:        9.015 s
Ran all test suites matching /tests\/identities\//i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-lz5iDI

  • - keys
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/keys

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/keys"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-zObQeT
 PASS  tests/keys/utils.test.ts (45.439 s)
  utils
    ✓ key pair copy (48 ms)
    ✓ to and from der encoding (173 ms)
    ✓ certificate copy (54 ms)
    ✓ encryption and decryption of private key (3988 ms)
    ✓ generates recovery code (5 ms)
    ✓ generating key pair from recovery code is deterministic (30026 ms)

 PASS  tests/keys/KeyManager.test.ts (64.907 s)
  KeyManager
    ✓ KeyManager readiness (831 ms)
    ✓ constructs root key pair, root cert, root certs and db key (709 ms)
    ✓ creates a recovery code and can recover from the same code (15273 ms)
    ✓ create deterministic keypair with recovery code (12575 ms)
    ✓ uses WorkerManager for generating root key pair (583 ms)
    ✓ encrypting and decrypting with root key (600 ms)
    ✓ uses WorkerManager for encryption and decryption with root key (749 ms)
    ✓ encrypting beyond maximum size (679 ms)
    ✓ signing and verifying with root key (676 ms)
    ✓ uses WorkerManager for signing and verifying with root key (654 ms)
    ✓ can change root key password (2169 ms)
    ✓ can reset root certificate (2515 ms)
    ✓ can reset root key pair (3007 ms)
    ✓ can renew root key pair (3053 ms)
    ✓ order of certificate chain should be leaf to root (5438 ms)
    dbKey
      ✓ Creates a key when started. (449 ms)
      ✓ Throws an exception when it fails to parse the key. (892 ms)
      ✓ key remains unchanged when resetting keys. (1362 ms)
      ✓ key remains unchanged when renewing keys. (1382 ms)

Test Suites: 2 passed, 2 total
Tests:       25 passed, 25 total
Snapshots:   0 total
Time:        65.288 s
Ran all test suites matching /tests\/keys/i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-zObQeT

  • - network
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/network

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/network"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-63H181
 PASS  tests/network/utils.test.ts (19.006 s)
  utils
    ✓ building addresses (9 ms)
    ✓ resolving zero IPs (12 ms)
    ✓ resolving hostnames (72 ms)

WARN:ConnectionReverse 127.0.0.1:39387:Server Error: ErrorConnectionEndTimeout
WARN:ConnectionForward 127.0.0.1:37994:Forward Error: ErrorConnectionEndTimeout
WARN:ConnectionReverse 127.0.0.1:36353:Reverse Error: ErrorConnectionEndTimeout
 PASS  tests/network/index.test.ts (23.976 s)
  network index
    ✓ grpc integration with unary and stream calls (1279 ms)
    ✓ client initiates end (1134 ms)
    ✓ server initiates end (1133 ms)
    ✓ forward initiates end (212 ms)
    ✓ reverse initiates end (213 ms)

ERROR:ForwardProxy CONNECT bad request:Failed CONNECT to 127.0.0.1:80 - ErrorForwardProxyAuth
ERROR:ForwardProxy CONNECT bad request:Failed CONNECT - ErrorForwardProxyMissingNodeId
ERROR:ForwardProxy CONNECT bad request:Failed CONNECT - ErrorForwardProxyInvalidUrl
ERROR:ForwardProxy port 0:Failed CONNECT to 127.0.0.1:0 - ErrorConnectionStart: Send failed (status: -22)
ERROR:ForwardProxy connection timeout:Failed CONNECT to 127.0.0.1:38702 - ErrorConnectionStartTimeout
WARN:ConnectionReverse 127.0.0.1:32828:Reverse Error: ErrorConnectionTimeout
WARN:ReverseProxy test:Failed connection from 127.0.0.1:32828 - ErrorConnectionComposeTimeout
WARN:ReverseProxy test:Failed connection from 127.0.0.1:50956 - ErrorCertChainEmpty: No certificates available to verify
WARN:ConnectionReverse 127.0.0.1:50956:Reverse Error: ErrorConnectionTimeout
 PASS  tests/network/ReverseProxy.test.ts (33.199 s)
  ReverseProxy
    ✓ reverseProxy readiness (14 ms)
    ✓ open connection to port 0 fails (19 ms)
    ✓ open connection timeout due to lack of ready signal (3007 ms)
    ✓ open connection success (6 ms)
    ✓ open connection to multiple clients (7 ms)
    ✓ closed connection due to ending server (4 ms)
    ✓ connect timeout due to hanging client (3113 ms)
    ✓ connect fails due to missing client certificates (2114 ms)
    ✓ connect success (60 ms)
    ✓ stopping the proxy with open connections (57 ms)

ERROR:ForwardProxy connection reset:Failed CONNECT to 127.0.0.1:40893 - ErrorConnectionStart: UTP_ECONNRESET
ERROR:ForwardProxy missing certificates:Failed CONNECT to 127.0.0.1:38421 - ErrorConnectionStart: 139774921742144:error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:ssl/record/rec_layer_s3.c:1543:SSL alert number 40

ERROR:ForwardProxy invalid node id:Failed CONNECT to 127.0.0.1:40959 - ErrorCertChainUnclaimed: Node ID is not claimed by any certificate
WARN:ConnectionForward 127.0.0.1:55597:Forward Error: ErrorConnectionTimeout
WARN:ConnectionForward 127.0.0.1:50359:Forward Error: ErrorConnectionTimeout
 PASS  tests/network/ForwardProxy.test.ts (37.837 s)
  ForwardProxy
    ✓ forward proxy readiness (20 ms)
    ✓ HTTP CONNECT bad request failures to the forward proxy (20 ms)
    ✓ connection to port 0 fails (27 ms)
    ✓ connection start timeout due to hanging remote (4016 ms)
    ✓ connection reset due to ending remote (5098 ms)
    ✓ open connection fails due to missing certificates (13 ms)
    ✓ HTTP CONNECT fails due to missing certificates (9 ms)
    ✓ open connection fails due to invalid node id (98 ms)
    ✓ HTTP CONNECT fails due to invalid node id (68 ms)
    ✓ open connection success - forward initiates end (48 ms)
    ✓ open connection success - reverse initiates end (38 ms)
    ✓ HTTP CONNECT success - forward initiates end (53 ms)
    ✓ HTTP CONNECT success - reverse initiates end (169 ms)
    ✓ HTTP CONNECT success - client initiates end (171 ms)
    ✓ HTTP CONNECT success by opening connection first (81 ms)
    ✓ open connection keepalive timeout (1052 ms)
    ✓ HTTP CONNECT keepalive timeout (1178 ms)
    ✓ stopping the proxy with open connections (83 ms)
    ✓ open connection to multiple servers (71 ms)

Test Suites: 4 passed, 4 total
Tests:       37 passed, 37 total
Snapshots:   0 total
Time:        38.231 s
Ran all test suites matching /tests\/network/i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-63H181

[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/nodes  

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/nodes"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-ivkt21
 PASS  tests/nodes/utils.test.ts (20.031 s)
  Nodes utils
    ✓ basic distance calculation (6 ms)
    ✓ calculates correct first bucket (bucket 0) (2 ms)
    ✓ calculates correct arbitrary bucket (bucket 63) (1 ms)
    ✓ calculates correct last bucket (bucket 255) (7 ms)

 PASS  tests/nodes/NodeGraph.test.ts (26.603 s)
  NodeGraph
    ✓ NodeGraph readiness (58 ms)
    ✓ finds correct node address (18 ms)
    ✓ unable to find node address (7 ms)
    ✓ adds a single node into a bucket (14 ms)
    ✓ adds multiple nodes into the same bucket (27 ms)
    ✓ adds a single node into different buckets (22 ms)
    ✓ deletes a single node (and removes bucket) (15 ms)
    ✓ deletes a single node (and retains remainder of bucket) (66 ms)
    ✓ enforces k-bucket size, removing least active node when a new node is discovered (175 ms)
    ✓ enforces k-bucket size, retaining all nodes if adding a pre-existing node (124 ms)
    ✓ retrieves all buckets (in expected lexicographic order) (24 ms)
    ✓ refreshes buckets (2475 ms)
    ✓ finds a single closest node (3 ms)
    ✓ finds 3 closest nodes (8 ms)
    ✓ finds the 20 closest nodes (91 ms)
    ✓ updates node (6 ms)

WARN:ConnectionReverse 127.0.0.1:11110:Server Error: ErrorConnectionEndTimeout
WARN:ConnectionReverse 127.0.0.1:11110:Reverse Error: ErrorConnectionEndTimeout
ERROR:NodeConnection Test:Failed CONNECT to 128.0.0.1:12345 - ErrorConnectionStart: Send failed (status: -22)
  console.error
    Failed to connect to 128.0.0.1:12345/?nodeId=viu1h4fem24hj1hna97objks92e00a4kjg49u6r9f0bal75v6h16g through proxy 127.0.0.1:41169 with status 502

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

WARN:ConnectionReverse 127.0.0.1:51753:Server Error: ErrorConnectionEndTimeout
WARN:ConnectionReverse 127.0.0.1:51753:Reverse Error: ErrorConnectionEndTimeout
WARN:ConnectionForward 127.0.0.2:11111:Forward Error: ErrorConnectionEndTimeout
WARN:ConnectionReverse 127.0.0.1:48687:Server Error: ErrorConnectionEndTimeout
WARN:ConnectionReverse 127.0.0.1:48687:Reverse Error: ErrorConnectionEndTimeout
WARN:ConnectionReverse 127.0.0.1:41101:Server Error: ErrorConnectionEndTimeout
WARN:ConnectionReverse 127.0.0.1:41101:Reverse Error: ErrorConnectionEndTimeout
WARN:ConnectionReverse 127.0.0.1:11110:Reverse Error: ErrorConnectionEndTimeout
 FAIL  tests/nodes/NodeConnection.test.ts (52.307 s)
  NodeConnection
    ✓ session readiness (1364 ms)
    ✓ connects to its target (via direct connection) (1291 ms)
    ✕ fails to connect to target (times out) (20002 ms)
    ✓ receives 20 closest local nodes from connected target (1383 ms)
    ○ skipped scans the servers vaults

  ● NodeConnection › fails to connect to target (times out)

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      351 |     await conn.destroy();
      352 |   });
    > 353 |   test('fails to connect to target (times out)', async () => {
          |   ^
      354 |     await expect(
      355 |       NodeConnection.createNodeConnection({
      356 |         targetNodeId: targetNodeId,

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/nodes/NodeConnection.test.ts:353:3)

ERROR:NodeManagerTest:Failed CONNECT to 125.0.0.1:55555 - ErrorConnectionStartTimeout
  console.error
    Failed to connect to 125.0.0.1:55555/?nodeId=vi3et1hrpv2m2lrplcm7cu913kr45v51cak54vm68anlbvuf83ra0 through proxy 127.0.0.1:42777 with status 504

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

WARN:ConnectionReverse 127.0.0.1:53094:Reverse Error: ErrorConnectionEndTimeout
WARN:ConnectionForward 0.0.0.0:47398:Forward Error: ErrorConnectionTimeout
WARN:ConnectionForward 0.0.0.0:47398:Forward Error: ErrorConnectionEndTimeout
ERROR:NodeManagerTest:Failed CONNECT to 127.0.0.2:22222 - ErrorConnectionStartTimeout
  console.error
    Failed to connect to 127.0.0.2:22222/?nodeId=vi3et1hrpv2m2lrplcm7cu913kr45v51cak54vm68anlbvuf83ra0 through proxy 127.0.0.1:36001 with status 504

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

WARN:ConnectionForward 0.0.0.0:37083:Forward Error: ErrorConnectionEndTimeout
WARN:NodeManagerTest:Failed Creating PolykeyAgent
 FAIL  tests/nodes/NodeManager.test.ts (321.699 s)
  NodeManager
    ✓ NodeManager readiness (3044 ms)
    ✓ pings node (73403 ms)
    ✓ finds node (local) (1012 ms)
    ✓ finds node (contacts remote node) (2182 ms)
    ✕ cannot find node (contacts remote node) (101976 ms)
    ✓ knows node (true and false case) (1930 ms)
    getConnectionToNode
      ✓ creates new connection to node (3388 ms)
      ✓ gets existing connection to node (3071 ms)
      ✓ concurrent connection creation to same target results in 1 connection (2801 ms)
      ✕ unable to create new connection to offline node (101817 ms)
    Cross signing claims
      ✕ can successfully cross sign a claim (1213 ms)

  ● NodeManager › getConnectionToNode › unable to create new connection to offline node

    : Timeout - Async callback was not invoked within the 100000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 100000 ms timeout specified by jest.setTimeout.Error:

      239 |       expect(finalConnLock?.lock.isLocked()).toBeFalsy();
      240 |     });
    > 241 |     test(
          |     ^
      242 |       'unable to create new connection to offline node',
      243 |       async () => {
      244 |         // Add the dummy node

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/nodes/NodeManager.test.ts:241:5)

  ● NodeManager › cannot find node (contacts remote node)

    : Timeout - Async callback was not invoked within the 100000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 100000 ms timeout specified by jest.setTimeout.Error:

      364 |     global.polykeyStartupTimeout,
      365 |   );
    > 366 |   test(
          |   ^
      367 |     'cannot find node (contacts remote node)',
      368 |     async () => {
      369 |       // Case 3: node exhausts all contacts and cannot find node

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/nodes/NodeManager.test.ts:366:3)

  ● NodeManager › Cross signing claims › can successfully cross sign a claim

    ErrorStatusLocked:

      51 |     if (!lock(statusLockFile.fd)) {
      52 |       await statusLockFile.close();
    > 53 |       throw new statusErrors.ErrorStatusLocked();
         |             ^
      54 |     }
      55 |     this.statusLockFile = statusLockFile;
      56 |     try {

      at Object.start (src/status/Status.ts:53:13)
      at Object.start (node_modules/@matrixai/async-init/src/StartStop.ts:56:22)
      at Function.createPolykeyAgent (src/PolykeyAgent.ts:169:7)
      at Object.<anonymous> (tests/nodes/NodeManager.test.ts:455:11)

  ● NodeManager › Cross signing claims › can successfully cross sign a claim

    TypeError: Cannot read property 'nodeManager' of undefined

      495 |       // 3. X -> sends doubly signed claim (Y's intermediary) + its own intermediary claim -> Y
      496 |       // 4. X <- sends doubly signed claim (X's intermediary) <- Y
    > 497 |       await y.nodeManager.claimNode(xNodeId);
          |               ^
      498 |
      499 |       // Check both sigchain locks are released
      500 |       expect(x.sigchain.locked).toBe(false);

      at Object.<anonymous> (tests/nodes/NodeManager.test.ts:497:15)

  ● NodeManager › Cross signing claims › can successfully cross sign a claim

    TypeError: Cannot read property 'sigchain' of undefined

      487 |     afterEach(async () => {
      488 |       await x.sigchain.clearDB();
    > 489 |       await y.sigchain.clearDB();
          |               ^
      490 |     });
      491 |
      492 |     test('can successfully cross sign a claim', async () => {

      at Object.<anonymous> (tests/nodes/NodeManager.test.ts:489:15)

A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks.
Test Suites: 2 failed, 2 passed, 4 total
Tests:       4 failed, 1 skipped, 31 passed, 36 total
Snapshots:   0 total
Time:        322.647 s
Ran all test suites matching /tests\/nodes/i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-ivkt21
npm ERR! Test failed.  See above for more details.


  • - notifications: failures in
    • NotificationsManager
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/notifications/

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/notifications/"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-Go05mX
 PASS  tests/notifications/utils.test.ts (14.031 s)
  Notifications utils
    ✓ generates notification ids (16 ms)
    ✓ Generator maintains order between instances (1030 ms)
    ✓ signs notifications (329 ms)
    ✓ verifies and decodes signed notifications (335 ms)
    ✓ validates correct notifications (3 ms)
    ✓ does not validate incorrect notifications (4 ms)

WARN:ConnectionForward 127.0.0.2:50357:Client Error: ErrorConnectionEndTimeout
  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:43253 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:43253 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:43253 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:43253 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:43253 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:41363 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:41363 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:41363 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:41363 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:41363 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:41363 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:41363 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:41305 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:41305 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:41305 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:41305 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:41305 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:41305 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:33029 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:33029 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:33029 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:33029 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:33029 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:33029 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:37885 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:37885 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:37885 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:37885 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:37885 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:37885 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:35757 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:35757 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:35757 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:35757 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

  console.error
    Failed to connect to 127.0.0.2:50357/?nodeId=vst975nnl15muuvlkkl5ih9jgapbvpp339ltbqqrv36i3j92ot4v0 through proxy 127.0.0.1:35757 with status 503

      at Object.<anonymous>.exports.log (node_modules/@grpc/grpc-js/src/logging.ts:57:13)
      at ClientRequest.<anonymous> (node_modules/@grpc/grpc-js/src/http_proxy.ts:269:9)

 FAIL  tests/notifications/NotificationsManager.test.ts (136.236 s)
  NotificationsManager
    ✓ can send notifications (1363 ms)
    ✕ can receive and read sent notifications (20041 ms)
    ✕ cannot receive notifications without notify permission (20030 ms)
    ✕ notifications are read in order they were sent (20030 ms)
    ✕ notifications can be capped (20033 ms)
    ✕ can send and receive Gestalt Invite notifications (20031 ms)
    ✕ can send and receive Vault Share notifications (20015 ms)

  ● NotificationsManager › can receive and read sent notifications

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      334 |   });
      335 |
    > 336 |   test('can receive and read sent notifications', async () => {
          |   ^
      337 |     const senderNotificationsManager =
      338 |       await NotificationsManager.createNotificationsManager({
      339 |         acl: senderACL,

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/notifications/NotificationsManager.test.ts:336:3)

  ● NotificationsManager › cannot receive notifications without notify permission

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      366 |   });
      367 |
    > 368 |   test('cannot receive notifications without notify permission', async () => {
          |   ^
      369 |     const senderNotificationsManager =
      370 |       await NotificationsManager.createNotificationsManager({
      371 |         acl: senderACL,

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/notifications/NotificationsManager.test.ts:368:3)

  ● NotificationsManager › notifications are read in order they were sent

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      395 |   });
      396 |
    > 397 |   test('notifications are read in order they were sent', async () => {
          |   ^
      398 |     const senderNotificationsManager =
      399 |       await NotificationsManager.createNotificationsManager({
      400 |         acl: senderACL,

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/notifications/NotificationsManager.test.ts:397:3)

  ● NotificationsManager › notifications can be capped

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      444 |   });
      445 |
    > 446 |   test('notifications can be capped', async () => {
          |   ^
      447 |     const senderNotificationsManager =
      448 |       await NotificationsManager.createNotificationsManager({
      449 |         acl: senderACL,

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/notifications/NotificationsManager.test.ts:446:3)

  ● NotificationsManager › can send and receive Gestalt Invite notifications

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      495 |   });
      496 |
    > 497 |   test('can send and receive Gestalt Invite notifications', async () => {
          |   ^
      498 |     const senderNotificationsManager =
      499 |       await NotificationsManager.createNotificationsManager({
      500 |         acl: senderACL,

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/notifications/NotificationsManager.test.ts:497:3)

  ● NotificationsManager › can send and receive Vault Share notifications

    : Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Error:

      526 |   });
      527 |
    > 528 |   test('can send and receive Vault Share notifications', async () => {
          |   ^
      529 |     const senderNotificationsManager =
      530 |       await NotificationsManager.createNotificationsManager({
      531 |         acl: senderACL,

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Suite.<anonymous> (tests/notifications/NotificationsManager.test.ts:528:3)

A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks.
Test Suites: 1 failed, 1 passed, 2 total
Tests:       6 failed, 7 passed, 13 total
Snapshots:   0 total
Time:        137.149 s
Ran all test suites matching /tests\/notifications\//i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-Go05mX
npm ERR! Test failed.  See above for more details.

  • - schema
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/schema

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/schema"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-3aNwLx
 PASS  tests/schema/Schema.test.ts
  Schema
    ✓ schema readiness (19 ms)
    ✓ creating schema (3 ms)
    ✓ creating schema with specific version and fresh (13 ms)
    ✓ destroying schema destroys underlying state (3 ms)
    ✓ concurrent version reads (2 ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        1.075 s
Ran all test suites matching /tests\/schema/i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-3aNwLx

  • - sessions
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/sessions/

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/sessions/"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-pq4qRF
 PASS  tests/sessions/Session.test.ts (5.084 s)
  Session
    ✓ session readiness (12 ms)
    ✓ creating session (5 ms)
    ✓ creating session with initial token (1 ms)
    ✓ creating fresh session (3 ms)
    ✓ destroying session destroys underlying token state (1 ms)
    ✓ concurrent session reads and writes (473 ms)
    ✓ simultaneous creation and destruction of session instances (392 ms)

 PASS  tests/sessions/SessionManager.test.ts (14.44 s)
  SessionManager
    ✓ session manager readiness (15 ms)
    ✓ creating and verifying session tokens (8 ms)
    ✓ checking expired session tokens (1107 ms)
    ✓ sessions key is persistent across restarts (16 ms)
    ✓ creating fresh session manager (12 ms)
    ✓ renewing key invalidates existing tokens (11 ms)
    ✓ concurrent token generation with key reset (479 ms)

Test Suites: 2 passed, 2 total
Tests:       14 passed, 14 total
Snapshots:   0 total
Time:        14.779 s
Ran all test suites matching /tests\/sessions\//i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-pq4qRF

  • - sigchain
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/sigchain/

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/sigchain/"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-tekjCu
 PASS  tests/sigchain/Sigchain.test.ts (17.952 s)
  Sigchain
    ✓ session readiness (1045 ms)
    ✓ async start initialises the sequence number (1076 ms)
    ✓ adds and retrieves a cryptolink, verifies signature (1178 ms)
    ✓ adds and retrieves 2 cryptolinks, verifies signatures and hash (1329 ms)
    ✓ adds an existing claim (1110 ms)
    ✓ retrieves chain data (1274 ms)
    ✓ retrieves all cryptolinks (nodes and identities) from sigchain (in expected lexicographic order) (1594 ms)

Test Suites: 1 passed, 1 total
Tests:       7 passed, 7 total
Snapshots:   0 total
Time:        17.988 s
Ran all test suites matching /tests\/sigchain\//i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-tekjCu

  • - status
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/status/  

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/status/"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-qYKNwA
 PASS  tests/status/Status.test.ts (10.371 s)
  Status
    ✓ status readiness (23 ms)
    ✓ status transitions (6 ms)
    ✓ start with existing statusPath or statusLockPath (2 ms)
    ✓ readStatus on non-existent status (1 ms)
    ✓ updating live status (14 ms)
    ✓ singleton running status (6 ms)
    ✓ wait for transitions (204 ms)
    ✓ parse error when statusPath is corrupted (3 ms)
    ✓ status transitions are serialised (1019 ms)
    ✓ wait for has at-least-once semantics (60 ms)

Test Suites: 1 passed, 1 total
Tests:       10 passed, 10 total
Snapshots:   0 total
Time:        10.426 s
Ran all test suites matching /tests\/status\//i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-qYKNwA

  • - validation
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/validation/

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/validation/"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-FSsRVl
 PASS  tests/validation/index.test.ts (6.716 s)
  validation/index
    ✓ validate primitives (4 ms)
    ✓ validate primitives - sync (1 ms)
    ✓ validate objects (3 ms)
    ✓ validate objects - sync (1 ms)
    ✓ validation error contains parse errors (6 ms)
    ✓ manipulate `this` when using function expressions (1 ms)

Test Suites: 1 passed, 1 total
Tests:       6 passed, 6 total
Snapshots:   0 total
Time:        6.752 s
Ran all test suites matching /tests\/validation\//i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-FSsRVl
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/vaults/

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/vaults/"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-K5KMRb
 PASS  tests/vaults/utils.test.ts (22.859 s)
  Vaults utils
    ✓ VaultId type guard works (13 ms)
    ✓ makeVaultId converts a buffer (8 ms)
    ○ skipped EFS can be read recursively
    ○ skipped can search for a vault name

 FAIL  tests/vaults/VaultManager.test.ts (109.155 s)
  VaultManager
    ✓ VaultManager readiness (2000 ms)
    ✓ is type correct (1950 ms)
    ✓ can create many vaults and open a vault (22533 ms)
    ✓ can open the same vault twice and perform mutations (4134 ms)
    ✓ can rename a vault (2876 ms)
    ✓ can delete a vault (3526 ms)
    ✓ can list vaults (3257 ms)
    ✓ able to read and load existing metadata (10998 ms)
    ✓ can concurrently rename the same vault (2378 ms)
    ✓ can concurrently open and rename the same vault (2179 ms)
    ✓ can save the commit state of a vault (3682 ms)
    ✓ able to recover metadata after complex operations (14383 ms)
    ○ skipped cannot concurrently create the same vault
    interacting with another node to
      ✕ clone and pull vaults (6870 ms)

  ● VaultManager › interacting with another node to › clone and pull vaults

    key should be a string or a buffer

      234 |   ): Promise<Array<DBOp>> {
      235 |     const bucketIndex = this.getBucketIndex(nodeId);
    > 236 |     let bucket = await this.db.get<NodeBucket>(
          |                                ^
      237 |       this.nodeGraphBucketsDbDomain,
      238 |       bucketIndex,
      239 |     );

      at node_modules/sublevel-prefixer/index.js:8:11
      at Object.domainPath (node_modules/@matrixai/db/src/utils.ts:14:14)
      at DB.get (node_modules/@matrixai/db/src/DB.ts:328:39)
      at Object.setNodeOps (src/nodes/NodeGraph.ts:236:32)
      at src/nodes/NodeGraph.ts:226:30
      at Object.transaction (src/nodes/NodeGraph.ts:129:20)
          at runMicrotasks (<anonymous>)
      at Object._transaction (src/nodes/NodeGraph.ts:143:14)
      at Object.setNode (src/nodes/NodeGraph.ts:225:12)

 PASS  tests/vaults/VaultInternal.test.ts (169.144 s)
  VaultInternal
    ✓ is type correct (5185 ms)
    ✓ VaultInternal readiness (2492 ms)
    ✓ creating state on disk (2416 ms)
    ✓ Accessing a change (4248 ms)
    ✓ Vault maintains data across VaultInternal instances (3681 ms)
    ✓ Vault only exposes limited commands of VaultInternal (2992 ms)
    version
      ✓ can change to the current commit (5224 ms)
      ✓ can change commits and preserve the log with no intermediate vault mutation (10662 ms)
      ✓ does not allow changing to an unrecognised commit (16908 ms)
      ✓ can change to the HEAD commit (11336 ms)
      ✓ adjusts HEAD after vault mutation, discarding forward and preserving backwards history (11740 ms)
    Writing operations
      ✓ Write operation allowed (3747 ms)
      ✓ Concurrent write operations prevented (9823 ms)
      ✓ Write locks read (5163 ms)
      ✓ Commit added if mutation in write (3537 ms)
      ✓ No commit added if no mutation in write (2754 ms)
      ✓ Commit message contains all actions made in the commit (8094 ms)
      ✓ No mutation to vault when part of a commit operation fails (4178 ms)
      ✓ Locking occurs when making a commit. (5638 ms)
    Reading operations
      ✓ Read operation allowed (3775 ms)
      ✓ Concurrent read operations allowed (3685 ms)
      ✓ Read locks write (5505 ms)
      ✓ No commit after read (3865 ms)
      ✓ Locking occurs when making an access. (4149 ms)

 PASS  tests/vaults/VaultOps.test.ts (237.36 s)
  VaultOps
    ✓ adding a secret (3650 ms)
    ✓ adding a secret and getting it (2695 ms)
    ✓ able to make directories (5767 ms)
    ✓ adding and committing a secret 10 times (52052 ms)
    ✓ updating secret content (4006 ms)
    ✓ updating secret content within a directory (5839 ms)
    ✓ updating a secret 10 times (17455 ms)
    ✓ deleting a secret (10230 ms)
    ✓ deleting a secret within a directory (4843 ms)
    ✓ deleting a secret 10 times (22056 ms)
    ✓ renaming a secret (3534 ms)
    ✓ renaming a secret within a directory (4900 ms)
    ✓ listing secrets (7154 ms)
    ✓ listing secret directories (4852 ms)
    ✓ adding hidden files and directories (4326 ms)
    ✓ updating and deleting hidden files and directories (13693 ms)
    ✓ adding a directory of 1 secret (1914 ms)
    ✓ adding a directory with subdirectories and files (4789 ms)
    ✓ testing the errors handling of adding secret directories (6166 ms)
    ✓ adding a directory of 100 secrets with some secrets already existing (25705 ms)

Test Suites: 1 failed, 3 passed, 4 total
Tests:       1 failed, 3 skipped, 58 passed, 62 total
Snapshots:   0 total
Time:        237.729 s
Ran all test suites matching /tests\/vaults\//i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-K5KMRb
npm ERR! Test failed.  See above for more details.

  • - workers
[nix-shell:~/Documents/MatrixAI/js-polykey]$ npm test -- tests/workers/   

> @matrixai/polykey@1.0.0 test /home/josh/Documents/MatrixAI/js-polykey
> jest "tests/workers/"

Determining test suites to run...
GLOBAL SETUP
Global Data Dir: /run/user/1000/polykey-test-global-s0rRbF
 PASS  tests/workers/polykeyWorker.test.ts (15.902 s)
  Polykey worker
    ✓ generateKeyPairAsn1 (10569 ms)
    ✓ encryptWithPublicKeyAsn1 (378 ms)
    ✓ decryptWithPrivateKeyAsn1 (996 ms)
    ✓ signWithPrivateKeyAsn1 (792 ms)
    ✓ verifyWithPublicKeyAsn1 (1089 ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        16.063 s
Ran all test suites matching /tests\/workers\//i.
GLOBAL TEARDOWN
Destroying Global Data Dir: /run/user/1000/polykey-test-global-s0rRbF

@joshuakarp
Copy link
Contributor

There's a lot of domains in tests/bin that are just straight up failing, mostly because of changes to expected outputs/exit codes, but there are also some that are experiencing timeout problems.

@CMCDragonkai
Copy link
Member Author

The status test was due to parsePort. It was needing the data to be a number.

I've made parsePort accept a string and if it is string, it will first try to parse it as an integer. Which sort of means that '34df' would be parsed as 34 which is due to the way parseInt works. Should be fine for now.

@tegefaulkes
Copy link
Contributor

the tests/bin/nodes/ping.test.ts is timing out when doing getClosestGlobalNodes` to find a node that doesn't exist. it touches a lot of code that has been updated and fixed in #310 So I don't think it's worth fixing here.

@CMCDragonkai
Copy link
Member Author

The tests/bin/agent is all working now.

@CMCDragonkai
Copy link
Member Author

CMCDragonkai commented Feb 7, 2022

The tests/notifications/NotificationManager.test.ts test failures have been fixed in test splitting #311 said @emmacasolin, so we can leave it for that.

The tests/bin/identities are all together and have not yet been split until #311, those tests can be fixed in #311 too.

@CMCDragonkai
Copy link
Member Author

Ok so remaining test failures to be covered in the 3 subsequent PRs:

…rder to encode the raw `NodeId` before `toJSON` method is called on `IdInternal`
@emmacasolin
Copy link
Contributor

I think there's a bug in ErrorValidation/grpcUtils.toError(). When we deserialise GRPC errors into ErrorPolykey errors we need to throw a new ErrorPolykey instance. The constructor for ErrorPolykey takes two parameters: message: string = '', data: POJO = {}, whereas ErrorValidation only takes one: errors: Array<ErrorParse> (this is because ErrorValidation is primarily used to convert ErrorParse errors into ErrorPolykey instances).

The problem is that grpcUtils.toError() creates new ErrorPolykey errors with the line return new errors[errorName](errorMessage, JSON.parse(errorData));, but since ErrorValidation expects errors: Array<ErrorParse> as its parameter it ends up treating errorMessage (which is just a string) as an array, straight away executing the line const message = errors.map((e) => e.message).join('; '); where errors is the errorMessage string. So this means that anytime an ErrorValidation is thrown inside any grpc handler we get the error TypeError: errors.map is not a function.

@CMCDragonkai
Copy link
Member Author

There may in fact be an error here. Because I did a quickfix and didn't think too much about it. Can you also reconcile your thoughts with #304 and #323?

@CMCDragonkai
Copy link
Member Author

Also I didn't really add any new tests involving validation errors at the GRPC level. This will need to be allocated a new PR or integrated into #275.

@emmacasolin
Copy link
Contributor

I think there's a bug in ErrorValidation/grpcUtils.toError(). When we deserialise GRPC errors into ErrorPolykey errors we need to throw a new ErrorPolykey instance. The constructor for ErrorPolykey takes two parameters: message: string = '', data: POJO = {}, whereas ErrorValidation only takes one: errors: Array<ErrorParse> (this is because ErrorValidation is primarily used to convert ErrorParse errors into ErrorPolykey instances).

The problem is that grpcUtils.toError() creates new ErrorPolykey errors with the line return new errors[errorName](errorMessage, JSON.parse(errorData));, but since ErrorValidation expects errors: Array<ErrorParse> as its parameter it ends up treating errorMessage (which is just a string) as an array, straight away executing the line const message = errors.map((e) => e.message).join('; '); where errors is the errorMessage string. So this means that anytime an ErrorValidation is thrown inside any grpc handler we get the error TypeError: errors.map is not a function.

A solution for this that appears to do the trick was to move the logic of deconstructing the array of ErrorParses out of ErrorValidation and into its own separate function that can be called inside validate and validateSync. Doing this means that ErrorValidate can properly extend ErrorPolykey using the same constructor, allowing it to be constructed the same way as any other ErrorPolykey inside grpcUtils.toError(). For now, the 'deconstruction' function is in src/validation/index.ts (not exported):

function deconstructErrorParse(errors: Array<validationErrors.ErrorParse>) {
  const message = errors.map((e) => e.message).join('; ');
  const data = {
    errors: errors.map((e) => ({
      message: e.message,
      keyPath: e.keyPath,
      value: e.value.valueOf(),
    })),
  };
  return { message, data };
}

And then instead of this line inside validate and validateSync:

throw new validationErrors.ErrorValidation(errors);

We have:

const errorData = deconstructErrorParse(errors);
throw new validationErrors.ErrorValidation(errorData.message, errorData.data);

@emmacasolin
Copy link
Contributor

emmacasolin commented Feb 10, 2022

With this change, this is the error that gets thrown by grpcUtils.toError() for an ErrorValidation constructed from two ErrorParses:

ErrorValidation: Provider ID length must be greater than 0; Identity ID length must be greater than 0
        at toError (/home/emma/Projects/js-polykey/src/grpc/utils/utils.ts:190:21)
        at Object.callback (/home/emma/Projects/js-polykey/src/grpc/utils/utils.ts:223:17)
        at Object.onReceiveStatus (/home/emma/Projects/js-polykey/node_modules/@grpc/grpc-js/src/client.ts:338:26)
        at Object.onReceiveStatus (/home/emma/Projects/js-polykey/node_modules/@grpc/grpc-js/src/client-interceptors.ts:426:34)
        at Object.onReceiveStatus (/home/emma/Projects/js-polykey/node_modules/@grpc/grpc-js/src/client-interceptors.ts:389:48)
        at /home/emma/Projects/js-polykey/node_modules/@grpc/grpc-js/src/call-stream.ts:276:24
        at processTicksAndRejections (internal/process/task_queues.js:77:11) {
      description: 'Input data failed validation',
      exitCode: 65,
      data: { errors: [ [Object], [Object] ] }
    }

Where this is the value for the data field:

{"errors":[{"message":"Provider ID length must be greater than 0","keyPath":["providerId"],"value":""},{"message":"Identity ID length must be greater than 0","keyPath":["identityId"],"value":""}]}

So it looks like it's performing as expected.

Note: I've also now added a field for context into the errors objects so all of the information from ErrorParse is contained in an ErrorValidation's data field. The validation tests are now passing (with a few very minor tweaks).

@CMCDragonkai
Copy link
Member Author

Suggest something like this:

class ErrorValidation extends ErrorPolykey {
  description = '...';
  exitCode = sysexits.DATAERR;
  public errors: Array<ErrorParse>;

  constructor(message, data) {

    if (data.errors != null) {
      let errors = [];
      for (const eData of data.errors) {
        const errorParse = new ErrorParse(eData.message);
        errorParse.keyPath = ...eData.keyPath;
        errors.push(errorParse);
      }
      this.errors = errors;
    }


  }

  static createFromErrors(errors: Array<ErrorParse>): ErrorValidation {
    const message = errors.map((e) => e.message).join('; ');
    const data = {
      errors: errors.map((e) => ({
        message: e.message,
        keyPath: e.keyPath,
        value: e.value.valueOf(),
      })),
    };
    const e = new ErrorValidation(message, data);
    e.errors = errors;
    return e;
    // super(message, data);
    // this.description = 'Input data failed validation';
    // this.exitCode = sysexits.DATAERR;
    // this.errors = errors;
  }
}

@CMCDragonkai
Copy link
Member Author

Get a new PR so you can describe this issue and merge into master. It's still a hotfix until we get a proper error chaining system.

@emmacasolin
Copy link
Contributor

Get a new PR so you can describe this issue and merge into master. It's still a hotfix until we get a proper error chaining system.

New PR here: #331

Will start implementing something along these lines: #321 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants