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

@Krunal2017 | Challenge2016 Solution #175

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,73 @@ To submit a solution, fork this repo and send a Pull Request on Github.

For any questions or clarifications, raise an issue on this repo and we'll answer your questions as fast as we can.

## Solution Description
The service starts an HTTP server to listen to incoming requests for creating distributors. It expects to receive the distributor data from POST request in the JSON format as given below:
```
{
"name": "DISTRIBUTOR2",
"inherits": "DISTRIBUTOR1",
"include": ["IN"],
"exclude": ["JK:IN"]
}
```
The location codes follow the format `<city>:<province>:<country>`. To include the entire province or country, codes such as `<province>:<country>` or `<country>` could be used.

The service can handle GET and POST requests currently, to perform the following operations:
1. GET requests: Can be used to query whether a distributor has access to a certain location code.
2. POST requests: Can be used to create new distributors. It returns a JSON response containing the data it has stored. It weeds out any locations the distributor cannot have access to.

In future following requests could also be implemented:
1. PATCH request: Can be used to update the include or exclude lists of a distributor
2. PUT request: Can be used to replace the existing distributor


### Running the service locally
To run the service locally, use the command:
```
go run all
```

The following message will be visible on the terminal if the server starts successfully:
```
challenge2016>go run all
Starting server on :8080...
```

### Limitations:
Currently, a distributor cannot be updated once it is created via POST request.

### GET request example
```
curl "http://localhost:8080/distributor?distributor=DISTRIBUTOR1&location=UDHAP:JK:IN"
```

### POST request example
```
curl -X POST http://localhost:8080/distributor -H "Content-Type: application/json" -d '{
"name": "DISTRIBUTOR1",
"include": ["IN", "WS"],
"exclude": ["UP:IN", "YAVTM:MH:IN"]
}'
```

### Expected responses
StatusOK - 200 : Distributor can access the given location
StatusCreated - 201 : Successfully created Distributor
StatusNotFound - 404 : Distributor not found
StatusForbidden - 403 : Distributor cannot access the given location
StatusBadRequest - 400 : Received invalid JSON input
StatusConflict - 409 : Distributor already exists
StatusInternalServerError - 500 : Error occurred while returning response

## Run Unit tests

Unit tests can be run using the command:
go test

## Bulk/Stress test:

The script test.py can be used as follows:
python3 test.py

This script can be used to generate a chain of 50 distributors by default. Script can be modified to test more number of distributors or increasing the number of cities being included by the distributors.
13 changes: 13 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

const (
ErrDistributorNotExist = "Error: distributor does not exist"
ErrDistributorExists = "Error: distributor already exists"
ErrInvalidJSONPayload = "Invalid JSON payload"
ErrInvalidJSONResponse = "Invalid JSON response"
ErrMissingParams = "Missing 'distributor' or 'location' parameter"
ErrMethodNotAllowed = "Only GET & POST methods are allowed"
MsgDistributorCreated = "Distributor created successfully"
MsgAccessGranted = "YES"
MsgAccessDenied = "NO"
)
52 changes: 52 additions & 0 deletions distributor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

// Request holds the input data
type Request struct {
Name string `json:"name,require"`
Include []string `json:"include"`
Exclude []string `json:"exclude"`
Inherits string `json:"inherits,omitempty"`
}

// Distributor holds the permissions for each distributor
type Distributor struct {
Name string
Include []string
Exclude []string
Inherits string
}

var distributorDB = make(map[string]*Distributor)

// createDistributor creates a new distributor or clones from a parent distributor if inheritance is defined.
func (d *Request) createDistributor() *Distributor {
if d.Inherits != "" {
// If Inherits is set, we clone from the parent distributor
parent := distributorDB[d.Inherits]
return parent.clone(d)
}
// If no inheritance, create a new distributor
return &Distributor{
Name: d.Name,
Include: d.Include,
Exclude: d.Exclude,
Inherits: d.Inherits, // Inheritance remains unchanged
}
}

// clone creates a new distributor based on the parent's permissions and merges the include/exclude lists
func (d *Distributor) clone(inData *Request) *Distributor {
var includeList []string
for _, code := range inData.Include {
res,_ := checkDistributor(d.Name, code)
if res == MsgAccessGranted {
includeList = append(includeList, code)
}
}
return &Distributor{
Name: inData.Name,
Include: includeList,
Exclude: append(d.Exclude, inData.Exclude...),
Inherits: inData.Inherits, // Inheritance remains unchanged
}
}
75 changes: 75 additions & 0 deletions distributor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package main

import (
"testing"
)

func TestCreateDistributor(t *testing.T) {
// Sample distributor data
request := &Request{
Name: "test-distributor",
Include: []string{"city:province:country"},
Exclude: []string{"city:province:anotherCountry"},
Inherits: "",
}

// Create distributor from request
distributor := request.createDistributor()

// Check that distributor is not nil
if distributor == nil {
t.Fatalf("Expected distributor to be created, but got nil")
}

// Check if the distributor has the correct name
if distributor.Name != request.Name {
t.Errorf("Expected distributor name to be '%s', but got '%s'", request.Name, distributor.Name)
}

// Check the Include and Exclude lists
if len(distributor.Include) != 1 || distributor.Include[0] != "city:province:country" {
t.Errorf("Expected include list to have 1 item: 'city:province:country', but got %v", distributor.Include)
}

if len(distributor.Exclude) != 1 || distributor.Exclude[0] != "city:province:anotherCountry" {
t.Errorf("Expected exclude list to have 1 item: 'city:province:anotherCountry', but got %v", distributor.Exclude)
}
}

func TestCloneDistributor(t *testing.T) {
// Sample parent distributor
parent := &Distributor{
Name: "parent-distributor",
Include: []string{"parentCountry"},
Exclude: []string{"excludeCountry"},
Inherits: "",
}

// Sample request to clone
request := &Request{
Name: "child-distributor",
Include: []string{"city:province:parentCountry"},
Exclude: []string{"city:province:excludeCountry"},
Inherits: "parent-distributor",
}

// Add parent to the global distributorDB
distributorDB["parent-distributor"] = parent

// Create distributor from request (it should clone from parent)
distributor := request.createDistributor()

// Check that the distributor has inherited the parent's data
if len(distributor.Include) != 1 || distributor.Include[0] != "city:province:parentCountry" {
t.Errorf("Expected include list to have 'city:province:parentCountry', but got %v", distributor.Include)
}

if len(distributor.Exclude) != 2 || distributor.Exclude[0] != "excludeCountry" || distributor.Exclude[1] != "city:province:excludeCountry" {
t.Errorf("Expected exclude list to have both 'city:province:excludeCountry', but got %v", distributor.Exclude)
}

// Check inheritance
if distributor.Inherits != "parent-distributor" {
t.Errorf("Expected distributor to inherit from 'parent-distributor', but got '%s'", distributor.Inherits)
}
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module challenge2016

go 1.21.1
24 changes: 24 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

import (
"fmt"
"log"
"net/http"
)

func startServer(){
http.HandleFunc("/distributor", requestHandler)

fmt.Println("Starting server on :8080...")
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatalf("Server failed to start: %v", err)
}
}

// Main driver logic
func main() {

startServer()

}
Loading