Add initial project files
This commit is contained in:
75
pkg/deesee/codec.go
Normal file
75
pkg/deesee/codec.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// Package deesee contains functions for encrypting and decrypting text.
|
||||
//
|
||||
// Important:
|
||||
// - The encryption is not secure at all, it's just a simple substitution cipher.
|
||||
// - Only the English language is supported.
|
||||
//
|
||||
// Ideas:
|
||||
// - write an encoder and construct like NewEncoder(key).Encode(any), but this is not required at this stage of the project.
|
||||
// - improve deesee-encoder to work with annotations, something like `deesee:"encode"` to prevent encoding for not authorized properties
|
||||
package deesee
|
||||
|
||||
import "strings"
|
||||
|
||||
const (
|
||||
a = 97 // ASCII code for a
|
||||
z = 122 // ASCII code for z
|
||||
)
|
||||
|
||||
// Encrypt text and returns an encrypted using the "DeeSee Chiffre" encryption.
|
||||
func Encrypt(text string, n int) string {
|
||||
return chiffre(text, n)
|
||||
}
|
||||
|
||||
// Decrypt text and returns a decrypted using the "DeeSee Chiffre" encryption.
|
||||
func Decrypt(text string, n int) string {
|
||||
return chiffre(text, -n)
|
||||
}
|
||||
|
||||
// chiffre function takes a string and a key as input and returns the DeeSee encoded string
|
||||
func chiffre(text string, key int) string {
|
||||
var (
|
||||
output strings.Builder // String builder to store the encoded string
|
||||
ascii int // ASCII value of the current character
|
||||
)
|
||||
//
|
||||
// Convert the text string to lowercase
|
||||
text = strings.ToLower(text)
|
||||
|
||||
// Let the key be in the range of letters
|
||||
key %= z - a - 1
|
||||
|
||||
// Iterate through each character in the text string
|
||||
for _, char := range text {
|
||||
// Convert the character to its ASCII value
|
||||
ascii = int(char)
|
||||
|
||||
// Encode only if character is a letter
|
||||
if isLetter(ascii) {
|
||||
// Shift the character by the key
|
||||
ascii += key
|
||||
|
||||
// Which direction are we shifting?
|
||||
if key > 0 {
|
||||
// If the character is now out of bounds
|
||||
if ascii > z {
|
||||
ascii = a + key - 2
|
||||
}
|
||||
} else {
|
||||
// If the character is now out of bounds
|
||||
if ascii < a {
|
||||
ascii = z + key + 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append the encoded character to the output string
|
||||
output.WriteRune(rune(ascii))
|
||||
}
|
||||
return output.String()
|
||||
}
|
||||
|
||||
// isLetter returns true if the given character is inside of ASCII letter diapason.
|
||||
func isLetter(char int) bool {
|
||||
return char >= a && char <= z
|
||||
}
|
||||
36
pkg/deesee/codec_test.go
Normal file
36
pkg/deesee/codec_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package deesee
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestEncrypt tests the Encrypt and Decrypt function.
|
||||
func TestCryptDecrypt(t *testing.T) {
|
||||
|
||||
// Case is a struct to store test cases.
|
||||
type Case struct {
|
||||
name string
|
||||
key int
|
||||
A string
|
||||
B string
|
||||
}
|
||||
|
||||
// Run the test cases.
|
||||
for _, tt := range []Case{
|
||||
{"Test encrypt-decrypt: simple text",
|
||||
5, "clark", "hqfwp"},
|
||||
{"Test encrypt-decrypt: text with shifted outbounds chars",
|
||||
3, "cherry blossom", "fkhuub eorvvrp"},
|
||||
{"Test encrypt-decrypt: text with shifted outbounds chars and non-letter chars",
|
||||
3, "cherry123 blossom!", "fkhuub123 eorvvrp!"},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
enc := Encrypt(tt.A, tt.key)
|
||||
if enc != tt.B {
|
||||
t.Errorf("Encrypt() = %v, want %v", enc, tt.B)
|
||||
}
|
||||
dec := Decrypt(enc, tt.key)
|
||||
if dec != tt.A {
|
||||
t.Errorf("Decrypt() = %v, want %v", dec, tt.A)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
39
pkg/deesee/filter_test.go
Normal file
39
pkg/deesee/filter_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package deesee
|
||||
|
||||
import (
|
||||
"github.com/eslider/superherohub/pkg/file"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Unit Test Coverage
|
||||
func TestFilterBySuperPowers(t *testing.T) {
|
||||
rootPath, err := file.GetModRootPath()
|
||||
if err != nil {
|
||||
t.Errorf("Expected GetModuleRootPath to return module root path, got error: %v", err)
|
||||
return
|
||||
}
|
||||
persons, err := Load(rootPath + "/data/heros.json")
|
||||
if err != nil {
|
||||
t.Errorf("Expected Load to return persons, got error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Test count loaded persons
|
||||
if len(persons) < 1 {
|
||||
t.Errorf("Expexted at least one person, got none")
|
||||
}
|
||||
|
||||
// Test with valid
|
||||
if len(SearchByPowers(persons, []string{"healing"})) < 1 {
|
||||
t.Errorf("Expected at least one person with healing superpower, got none")
|
||||
}
|
||||
// Test with valid and invalid
|
||||
if len(SearchByPowers(persons, []string{"healing", "slowpoking"})) < 1 {
|
||||
t.Errorf("Expected at least one person with healing superpower, got none")
|
||||
}
|
||||
|
||||
// Test with invalid
|
||||
if len(SearchByPowers(persons, []string{"programming"})) > 1 {
|
||||
t.Errorf("Expected no one person with programming superpower, got some")
|
||||
}
|
||||
}
|
||||
124
pkg/deesee/model.go
Normal file
124
pkg/deesee/model.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package deesee
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/eslider/superherohub/pkg/people"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// superPower is a type for superpowers.
|
||||
type superPower string
|
||||
|
||||
// List of allowed superpowers.
|
||||
const (
|
||||
strength superPower = "strength"
|
||||
speed superPower = "speed"
|
||||
flight superPower = "flight"
|
||||
invulnerability superPower = "invulnerability"
|
||||
healing superPower = "healing"
|
||||
)
|
||||
|
||||
// To keep the system as simple as possible,
|
||||
// DeeSee has agreed to only accept superheroes with the following superpowers.
|
||||
var allowedSuperPowers = [5]superPower{strength, speed, flight, invulnerability, healing}
|
||||
|
||||
// Superhero DeeSee person type.
|
||||
type Superhero people.Person
|
||||
|
||||
// IsAcceptable checks if the superhero is acceptable.
|
||||
func (s *Superhero) IsAcceptable() (r bool) {
|
||||
// Check if the person has superpowers.
|
||||
if *s.SuperPowers == nil {
|
||||
return false
|
||||
}
|
||||
// Check if the person has at least one acceptable superpower.
|
||||
for _, p := range *s.SuperPowers {
|
||||
if IsAcceptable(p) {
|
||||
r = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// IsSuperHero checks if the superhero is acceptable.
|
||||
func (s *Superhero) IsSuperHero() bool {
|
||||
return s.IsAcceptable()
|
||||
}
|
||||
|
||||
// Has checks if the superhero has the given power.
|
||||
func (s *Superhero) Has(power string) bool {
|
||||
return s.SuperPowers.Contains(power)
|
||||
}
|
||||
|
||||
// IsAcceptable checks if the given power is allowed.
|
||||
func IsAcceptable(power string) bool {
|
||||
for _, p := range allowedSuperPowers {
|
||||
if string(p) == power {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Load JSON file and decode to `[]*Person` list
|
||||
func Load(path string) (l []*Superhero, err error) {
|
||||
var f *os.File
|
||||
if f, err = os.Open(path); err != nil {
|
||||
return
|
||||
}
|
||||
// Parse the request body
|
||||
err = json.NewDecoder(f).Decode(&l)
|
||||
return
|
||||
}
|
||||
|
||||
// EncryptHerosIdentities encrypts all persons identities with DeeSee encryption algorithm
|
||||
func EncryptHerosIdentities(heros []*Superhero, key int) (r []*Superhero) {
|
||||
for _, person := range heros {
|
||||
p := &Superhero{
|
||||
Name: person.Name,
|
||||
Identity: EncryptIdentity(person.Identity, key),
|
||||
SuperPowers: person.SuperPowers,
|
||||
Birthday: person.Birthday,
|
||||
}
|
||||
r = append(r, p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// EncryptIdentity encrypts person identity with DeeSee encryption algorithm
|
||||
func EncryptIdentity(identity *people.Identity, key int) *people.Identity {
|
||||
return &people.Identity{
|
||||
FirstName: Encrypt(identity.FirstName, key),
|
||||
LastName: Encrypt(identity.LastName, key),
|
||||
}
|
||||
}
|
||||
|
||||
// SearchByPowers from persons list
|
||||
func SearchByPowers(heros []*Superhero, powers []string) (r []*Superhero) {
|
||||
for _, hero := range heros {
|
||||
for _, power := range powers {
|
||||
if hero.IsSuperHero() && hero.Has(power) {
|
||||
r = append(r, hero)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NewSuperhero creates a new superhero
|
||||
func NewSuperhero() *Superhero {
|
||||
return &Superhero{}
|
||||
}
|
||||
|
||||
// FindByName from superheros list
|
||||
func FindByName(heros []*Superhero, name string) *Superhero {
|
||||
for _, hero := range heros {
|
||||
if strings.EqualFold(name, hero.Name) {
|
||||
return hero
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user