Skip to content

ferpar/indigitest

Repository files navigation

Indigitall tech proof


Setup Instructions

There are three options for a quick setup:

Using docker-compose:

  1. Run the following command from the projects root directory: docker-compose up or sudo docker-compose up

The database information is permanently stored at ./dockerdata via a docker volume. Which will be created by docker-compose if not readily available. Make sure to give docker-compose the necessary permissions (via sudo if necessary).

Using docker for the database:

  1. Clone repo.

  2. Install dependencies as usual. npm install

  3. Move to the scripts directory

  4. Build the database image using the Dockerfile at the scripts folder, i.e.: docker build -t indipostgres .

  5. Run the database container: docker run --name {container_name} -p 5432:5432 -e POSTGRES_PASSWORD=mysecpass -d indipostgres

  6. Start the server npm start

Port and password must fit the values found in the config files found @ ./src/db/development.js and .src/db/test.js

{
  "user": "postgres",
  "password": "mysecpass",
  "database": "indigitest",
  "port": "5432",
  "host": "localhost"		
}

Without docker:

  1. Clone repo
  2. Install dependencies: npm install as usual
  3. If your postgres database is not listening on the default port 5432, change the port at ./src/db/development.js and .src/db/test.js.
  4. Making sure that postgres is running, run the database intialization script: ./scripts/initAcreateDb.sh Otherwise, make sure you create both databases as in the .sh file above.
  5. Finally, run the server: npm start

Description

Project structure

The project follows a layered architecture, where the applicattion core (domain) has the minimum possible outbound dependencies. As expected of a RESTful API, it is Event-Driven and part of a Client-Server distributed system.

Emphasis was put on controlling dependency-direction so that the domain layer is independent, and dependencies flow up from the infrastructure details (web server and database) to the domain model.

Please note how the service "user-actions" does not directly depend on the database. This was achieved by injecting the db into the service by means of a factory function @ /src/domain/user-service/index.js.

depgraph

The dependency injection pattern has been used throughout the application. It allows to work separately on individual components on a test-driven basis (write tests first, then the code to fulfill the test assertions).

In a more object oriented fashion, this could also have been accomplished using typescript's interfaces.

API

The API has been defined according to the openAPI specification. You may inspect it at swaggerhub or take a look at the openAPI.yaml within the assets folder.

It comprises the following endpoints:

endpoints

Database

ORM Conceptual Schema

The universe of discourse for this test produces the following ORM Conceptual Schema.

Here, the most complex elementary fact is given by: "User(.id) befriends User(.id)", where the verb befriend was chosen to avoid a symmetric relationship such as "User(.id) is friend of User(.id)".

ORM

The circular symbol attached to the binary roles is an assymetry constraint, meaning if we insert the fact "User1 befriends User2", it will no longer be possible to insert "User2 befriends User1" until the former relationship is removed. It also implies irreflexivity preventing the insertion of facts such as "User1 befriends User1" ( i.e.: in our model, a user may not be his own friend).

Relational Schema

Given the many to many relationship between users who befriend each other two tables are required.

SQLSchema

RDMS Instructions

Note how the assymetry condition on the friendship relation is being enforced via:

  • irreflexive CONSTRAINT @ friendship table declaration
  • enforce_Asymmetry TRIGGER
--create schema/database within psql
CREATE DATABASE indigitest;

--connect to the database schema
\c indigitest

--create tables and trigger
CREATE TABLE Users ( 
	userId VARCHAR(25) NOT NULL PRIMARY KEY,
	email VARCHAR(30) NOT NULL,
	password VARCHAR(64) NOT NULL,
	username VARCHAR(30) NOT NULL,
	browserLang VARCHAR(10),
	latitude FLOAT8,
	longitude FLOAT8);

CREATE TABLE friendships(
	befriender VARCHAR(25) NOT NULL references Users on delete cascade,
	userId VARCHAR(25) NOT NULL references Users on delete cascade,
	CONSTRAINT irreflexive check (befriender <> userId),
	PRIMARY KEY (befriender, userId));

CREATE FUNCTION find_sym_trig() RETURNS trigger AS $find_sym_trig$
	BEGIN
		IF ( EXISTS(SELECT * FROM friendships WHERE befriender = NEW.userid AND userid = NEW.befriender) ) THEN
			RAISE EXCEPTION 'friendship already stored as symmetric';
		END IF;
		RETURN NEW;
	END;
$find_sym_trig$ LANGUAGE plpgsql;

CREATE TRIGGER enforce_asymmetry BEFORE INSERT ON friendships
	FOR EACH ROW EXECUTE PROCEDURE find_sym_trig();

Note: The same is done for the database used for integration testing (indintdb), as can be seen in the intialization script @ ./src/scripts/initAcreateDb.sh .

Tests

Summary

This project was coded according to the TDD methodology in that test specifications almost always preceded the source-code implementation. The process started with the creation of a test specification for the user model and ended with the integration tests for the API (using supertest).

List

Test type Tested Component(s)/System(s) Test Spec
Unitary User Model user.spec.js
Unitary User Service user-service.spec.js
Unitary User Controller use-endpoint.spec.js
Unitary User Controller friend-endpoint.spec.js
Integration Database Accessor/Interface, Database user-db.spec.js
Integration whole API integration.spec.js

Coverage

As a byproduct of this methodology high code coverage is achieved without having to put emphasis on it.

Coverage

About

tech proof API

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published