Compare commits

..

No commits in common. "62f192630548bc7e1c4aa78e683c3753632a7e4a" and "7f5270f536597c31acfff8e0614d1ab516c1d0b0" have entirely different histories.

21 changed files with 142 additions and 389 deletions

View file

@ -27,10 +27,6 @@ clean: remove-podman-containers
cd backend && make clean
@echo "Cleaned up!"
.PHONY: itest
itest:
python testing.py
# Cleans up everything related to podman, not just the project. Make sure you understand what this means.
podman-clean:
podman system reset --force

View file

@ -4,6 +4,7 @@ import (
"embed"
"os"
"path/filepath"
"time"
"ttime/internal/types"
"github.com/jmoiron/sqlx"
@ -14,14 +15,13 @@ import (
type Database interface {
// Insert a new user into the database, password should be hashed before calling
AddUser(username string, password string) error
CheckUser(username string, password string) bool
RemoveUser(username string) error
PromoteToAdmin(username string) error
GetUserId(username string) (int, error)
AddProject(name string, description string, username string) error
Migrate(dirname string) error
GetProjectId(projectname string) (int, error)
AddWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error
AddTimeReport(projectName string, userName string, activityType string, start time.Time, end time.Time) error
AddUserToProject(username string, projectname string, role string) error
ChangeUserRole(username string, projectname string, role string) error
GetAllUsersProject(projectname string) ([]UserProjectMember, error)
@ -50,16 +50,27 @@ var scripts embed.FS
const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)"
const projectInsert = "INSERT INTO projects (name, description, owner_user_id) SELECT ?, ?, id FROM users WHERE username = ?"
const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?"
const addWeeklyReport = `WITH UserLookup AS (SELECT id FROM users WHERE username = ?),
const addTimeReport = `WITH UserLookup AS (SELECT id FROM users WHERE username = ?),
ProjectLookup AS (SELECT id FROM projects WHERE name = ?)
INSERT INTO weekly_reports (project_id, user_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time)
VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup),?, ?, ?, ?, ?, ?, ?);`
INSERT INTO time_reports (project_id, user_id, activity_type, start, end)
VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup),?, ?, ?);`
const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)" // WIP
const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?"
const getProjectsForUser = `SELECT projects.id, projects.name, projects.description, projects.owner_user_id
FROM projects JOIN user_roles ON projects.id = user_roles.project_id
JOIN users ON user_roles.user_id = users.id WHERE users.username = ?;`
const getProjectsForUser = `
SELECT
projects.id,
projects.name,
projects.description,
projects.owner_user_id
FROM
projects
JOIN
user_roles ON projects.id = user_roles.project_id
JOIN
users ON user_roles.user_id = users.id
WHERE
users.username = ?;`
// DbConnect connects to the database
func DbConnect(dbpath string) Database {
@ -78,15 +89,6 @@ func DbConnect(dbpath string) Database {
return &Db{db}
}
func (d *Db) CheckUser(username string, password string) bool {
var dbPassword string
err := d.Get(&dbPassword, "SELECT password FROM users WHERE username = ?", username)
if err != nil {
return false
}
return dbPassword == password
}
// GetProjectsForUser retrieves all projects associated with a specific user.
func (d *Db) GetProjectsForUser(username string) ([]types.Project, error) {
var projects []types.Project
@ -108,8 +110,9 @@ func (d *Db) GetProject(projectId int) (types.Project, error) {
return project, err
}
func (d *Db) AddWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error {
_, err := d.Exec(addWeeklyReport, userName, projectName, week, developmentTime, meetingTime, adminTime, ownWorkTime, studyTime, testingTime)
// AddTimeReport adds a time report for a specific project and user.
func (d *Db) AddTimeReport(projectName string, userName string, activityType string, start time.Time, end time.Time) error { // WIP
_, err := d.Exec(addTimeReport, userName, projectName, activityType, start, end)
return err
}

View file

@ -2,6 +2,7 @@ package database
import (
"testing"
"time"
)
// Tests are not guaranteed to be sequential
@ -92,7 +93,7 @@ func TestPromoteToAdmin(t *testing.T) {
}
}
func TestAddWeeklyReport(t *testing.T) {
func TestAddTimeReport(t *testing.T) {
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
@ -108,9 +109,12 @@ func TestAddWeeklyReport(t *testing.T) {
t.Error("AddProject failed:", err)
}
err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1)
var now = time.Now()
var then = now.Add(time.Hour)
err = db.AddTimeReport("testproject", "testuser", "activity", now, then)
if err != nil {
t.Error("AddWeeklyReport failed:", err)
t.Error("AddTimeReport failed:", err)
}
}
@ -130,9 +134,12 @@ func TestAddUserToProject(t *testing.T) {
t.Error("AddProject failed:", err)
}
err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1)
var now = time.Now()
var then = now.Add(time.Hour)
err = db.AddTimeReport("testproject", "testuser", "activity", now, then)
if err != nil {
t.Error("AddWeeklyReport failed:", err)
t.Error("AddTimeReport failed:", err)
}
err = db.AddUserToProject("testuser", "testproject", "user")

View file

@ -0,0 +1,22 @@
CREATE TABLE IF NOT EXISTS time_reports (
id INTEGER PRIMARY KEY,
project_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
activity_type TEXT NOT NULL,
start DATETIME NOT NULL,
end DATETIME NOT NULL,
FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
FOREIGN KEY (activity_type) REFERENCES activity_types (name) ON DELETE CASCADE
);
CREATE TRIGGER IF NOT EXISTS time_reports_start_before_end
BEFORE INSERT ON time_reports
FOR EACH ROW
BEGIN
SELECT
CASE
WHEN NEW.start >= NEW.end THEN
RAISE (ABORT, 'start must be before end')
END;
END;

View file

@ -1,14 +0,0 @@
CREATE TABLE weekly_reports (
user_id INTEGER,
project_id INTEGER,
week INTEGER,
development_time INTEGER,
meeting_time INTEGER,
admin_time INTEGER,
own_work_time INTEGER,
study_time INTEGER,
testing_time INTEGER,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (project_id) REFERENCES projects(id)
PRIMARY KEY (user_id, project_id, week)
)

View file

@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS report_collection (
id INTEGER PRIMARY KEY,
owner_id INTEGER NOT NULL,
project_id INTEGER NOT NULL,
date DATE NOT NULL,
signed_by INTEGER, -- NULL if not signed
FOREIGN KEY (owner_id) REFERENCES users (id)
FOREIGN KEY (signed_by) REFERENCES users (id)
);

View file

@ -0,0 +1,16 @@
-- It is unclear weather this table will be used
-- Create the table to store hash salts
CREATE TABLE IF NOT EXISTS salts (
id INTEGER PRIMARY KEY,
salt TEXT NOT NULL
);
-- Commented out for now, no time for good practices, which is atrocious
-- Create a trigger to automatically generate a salt when inserting a new user record
-- CREATE TRIGGER generate_salt_trigger
-- AFTER INSERT ON users
-- BEGIN
-- INSERT INTO salts (salt) VALUES (randomblob(16));
-- UPDATE users SET salt_id = (SELECT last_insert_rowid()) WHERE id = new.id;
-- END;

View file

@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS activity_types (
name TEXT PRIMARY KEY
);
INSERT OR IGNORE INTO activity_types (name) VALUES ('Development');
INSERT OR IGNORE INTO activity_types (name) VALUES ('Meeting');
INSERT OR IGNORE INTO activity_types (name) VALUES ('Administration');
INSERT OR IGNORE INTO activity_types (name) VALUES ('Own Work');
INSERT OR IGNORE INTO activity_types (name) VALUES ('Studies');
INSErt OR IGNORE INTO activity_types (name) VALUES ('Testing');

View file

@ -18,7 +18,6 @@ type GlobalState interface {
LoginRenew(c *fiber.Ctx) error // To renew the token
CreateProject(c *fiber.Ctx) error // To create a new project
GetUserProjects(c *fiber.Ctx) error // To get all projects
SubmitWeeklyReport(c *fiber.Ctx) error
// GetProject(c *fiber.Ctx) error // To get a specific project
// UpdateProject(c *fiber.Ctx) error // To update a project
// DeleteProject(c *fiber.Ctx) error // To delete a project
@ -78,17 +77,12 @@ func (gs *GState) Register(c *fiber.Ctx) error {
// This path should obviously be protected in the future
// UserDelete deletes a user from the database
func (gs *GState) UserDelete(c *fiber.Ctx) error {
// Read from path parameters
username := c.Params("username")
// Read username from Locals
auth_username := c.Locals("user").(*jwt.Token).Claims.(jwt.MapClaims)["name"].(string)
if username != auth_username {
return c.Status(403).SendString("You can only delete yourself")
u := new(types.User)
if err := c.BodyParser(u); err != nil {
return c.Status(400).SendString(err.Error())
}
if err := gs.Db.RemoveUser(username); err != nil {
if err := gs.Db.RemoveUser(u.Username); err != nil {
return c.Status(500).SendString(err.Error())
}
@ -106,20 +100,18 @@ func (gs *GState) IncrementButtonCount(c *fiber.Ctx) error {
// Login is a simple login handler that returns a JWT token
func (gs *GState) Login(c *fiber.Ctx) error {
// The body type is identical to a NewUser
u := new(types.NewUser)
if err := c.BodyParser(u); err != nil {
return c.Status(400).SendString(err.Error())
}
// To test: curl --data "user=user&pass=pass" http://localhost:8080/api/login
user := c.FormValue("user")
pass := c.FormValue("pass")
if !gs.Db.CheckUser(u.Username, u.Password) {
println("User not found")
// Throws Unauthorized error
if user != "user" || pass != "pass" {
return c.SendStatus(fiber.StatusUnauthorized)
}
// Create the Claims
claims := jwt.MapClaims{
"name": u.Username,
"name": user,
"admin": false,
"exp": time.Now().Add(time.Hour * 72).Unix(),
}
@ -167,9 +159,9 @@ func (gs *GState) CreateProject(c *fiber.Ctx) error {
// Get the username from the token and set it as the owner of the project
// This is ugly but
claims := user.Claims.(jwt.MapClaims)
owner := claims["name"].(string)
p.Owner = claims["name"].(string)
if err := gs.Db.AddProject(p.Name, p.Description, owner); err != nil {
if err := gs.Db.AddProject(p.Name, p.Description, p.Owner); err != nil {
return c.Status(500).SendString(err.Error())
}
@ -255,29 +247,3 @@ func (gs *GState) GetProject(c *fiber.Ctx) error {
// Return the project as JSON
return c.JSON(project)
}
func (gs *GState) SubmitWeeklyReport(c *fiber.Ctx) error {
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
report := new(types.NewWeeklyReport)
if err := c.BodyParser(report); err != nil {
return c.Status(400).SendString(err.Error())
}
// Make sure all the fields of the report are valid
if report.Week < 1 || report.Week > 52 {
return c.Status(400).SendString("Invalid week number")
}
if report.DevelopmentTime < 0 || report.MeetingTime < 0 || report.AdminTime < 0 || report.OwnWorkTime < 0 || report.StudyTime < 0 || report.TestingTime < 0 {
return c.Status(400).SendString("Invalid time report")
}
if err := gs.Db.AddWeeklyReport(report.ProjectName, username, report.Week, report.DevelopmentTime, report.MeetingTime, report.AdminTime, report.OwnWorkTime, report.StudyTime, report.TestingTime); err != nil {
return c.Status(500).SendString(err.Error())
}
return c.Status(200).SendString("Time report added")
}

View file

@ -1,21 +0,0 @@
package types
// This is what should be submitted to the server, the username will be derived from the JWT token
type NewWeeklyReport struct {
// The name of the project, as it appears in the database
ProjectName string `json:"projectName"`
// The week number
Week int `json:"week"`
// Total time spent on development
DevelopmentTime int `json:"developmentTime"`
// Total time spent in meetings
MeetingTime int `json:"meetingTime"`
// Total time spent on administrative tasks
AdminTime int `json:"adminTime"`
// Total time spent on personal projects
OwnWorkTime int `json:"ownWorkTime"`
// Total time spent on studying
StudyTime int `json:"studyTime"`
// Total time spent on testing
TestingTime int `json:"testingTime"`
}

View file

@ -8,8 +8,9 @@ type Project struct {
Owner string `json:"owner" db:"owner_user_id"`
}
// As it arrives from the client, Owner is derived from the JWT token
// As it arrives from the client
type NewProject struct {
Name string `json:"name"`
Description string `json:"description"`
Owner string `json:"owner"`
}

View file

@ -16,7 +16,6 @@ func (u *User) ToPublicUser() (*PublicUser, error) {
}, nil
}
// Should be used when registering, for example
type NewUser struct {
Username string `json:"username"`
Password string `json:"password"`

View file

@ -68,10 +68,9 @@ func main() {
SigningKey: jwtware.SigningKey{Key: []byte("secret")},
}))
server.Post("/api/submitReport", gs.SubmitWeeklyReport)
server.Get("/api/getUserProjects", gs.GetUserProjects)
server.Post("/api/loginrenew", gs.LoginRenew)
server.Delete("/api/userdelete/:username", gs.UserDelete) // Perhaps just use POST to avoid headaches
server.Delete("/api/userdelete", gs.UserDelete) // Perhaps just use POST to avoid headaches
server.Post("/api/project", gs.CreateProject)
// Announce the port we are listening on and start the server

View file

@ -1,6 +1,5 @@
import { NewProject, Project } from "../Types/Project";
import { NewUser, User } from "../Types/Users";
import { NewWeeklyReport } from "../Types/goTypes";
// This type of pattern should be hard to misuse
interface APIResponse<T> {
@ -21,13 +20,8 @@ interface API {
project: NewProject,
token: string,
): Promise<APIResponse<Project>>;
/** Submit a weekly report */
submitWeeklyReport(project: NewWeeklyReport, token: string): Promise<APIResponse<Project>>;
/** Renew the token */
renewToken(token: string): Promise<APIResponse<string>>;
/** Gets all the projects of a user*/
getUserProjects(username: string, token: string): Promise<APIResponse<Project[]>>;
}
// Export an instance of the API
@ -55,7 +49,7 @@ export const api: API = {
async removeUser(
username: string,
token: string
token: string,
): Promise<APIResponse<User>> {
try {
const response = await fetch("/api/userdelete", {
@ -80,7 +74,7 @@ export const api: API = {
async createProject(
project: NewProject,
token: string
token: string,
): Promise<APIResponse<Project>> {
try {
const response = await fetch("/api/project", {
@ -123,51 +117,4 @@ export const api: API = {
return { success: false, message: "Failed to renew token" };
}
},
async getUserProjects(token: string): Promise<APIResponse<Project[]>> {
try {
const response = await fetch("/api/getUserProjects", {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
});
if (!response.ok) {
return Promise.resolve({ success: false, message: "Failed to get user projects" });
} else {
const data = (await response.json()) as Project[];
return Promise.resolve({ success: true, data });
}
}
catch (e) {
return Promise.resolve({ success: false, message: "Failed to get user projects" });
}
},
submitWeeklyReport: function (project: NewWeeklyReport, token: string): Promise<APIResponse<Project>> {
try {
return fetch("/api/submitWeeklyReport", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify(project),
})
.then((response) => {
if (!response.ok) {
return { success: false, message: "Failed to submit weekly report" };
} else {
return response.json();
}
})
.then((data) => {
return { success: true, data };
});
} catch (e) {
return Promise.resolve({ success: false, message: "Failed to submit weekly report" });
}
}
};

View file

@ -1,32 +1,30 @@
import { useState } from "react";
import { NewWeeklyReport } from "../Types/goTypes";
import { TimeReport } from "../Types/TimeReport";
import { api } from "../API/API";
import { useNavigate } from "react-router-dom";
import Button from "./Button";
export default function NewTimeReport(): JSX.Element {
const [projectName, setProjectName] = useState("");
const [week, setWeek] = useState(0);
const [developmentTime, setDevelopmentTime] = useState(0);
const [meetingTime, setMeetingTime] = useState(0);
const [adminTime, setAdminTime] = useState(0);
const [ownWorkTime, setOwnWorkTime] = useState(0);
const [studyTime, setStudyTime] = useState(0);
const [testingTime, setTestingTime] = useState(0);
const [week, setWeek] = useState("");
const [development, setDevelopment] = useState("0");
const [meeting, setMeeting] = useState("0");
const [administration, setAdministration] = useState("0");
const [ownwork, setOwnWork] = useState("0");
const [studies, setStudies] = useState("0");
const [testing, setTesting] = useState("0");
const handleNewTimeReport = async (): Promise<void> => {
const newTimeReport: NewWeeklyReport = {
projectName,
const newTimeReport: TimeReport = {
week,
developmentTime,
meetingTime,
adminTime,
ownWorkTime,
studyTime,
testingTime,
development,
meeting,
administration,
ownwork,
studies,
testing,
};
await Promise.resolve();
// await api.submitWeeklyReport(newTimeReport, token); Token is not defined yet
// await api.registerTimeReport(newTimeReport); This needs to be implemented!
};
const navigate = useNavigate();
@ -36,7 +34,7 @@ export default function NewTimeReport(): JSX.Element {
<div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center">
<form
onSubmit={(e) => {
if (week === 0) {
if (week === "") {
alert("Please enter a week number");
e.preventDefault();
return;
@ -52,7 +50,7 @@ export default function NewTimeReport(): JSX.Element {
type="week"
placeholder="Week"
onChange={(e) => {
const weekNumber = parseInt(e.target.value.split("-W")[1]);
const weekNumber = e.target.value.split("-W")[1];
setWeek(weekNumber);
}}
onKeyDown={(event) => {
@ -81,9 +79,9 @@ export default function NewTimeReport(): JSX.Element {
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={developmentTime}
value={development}
onChange={(e) => {
setDevelopmentTime(parseInt(e.target.value));
setDevelopment(e.target.value);
}}
onKeyDown={(event) => {
const keyValue = event.key;
@ -100,9 +98,9 @@ export default function NewTimeReport(): JSX.Element {
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={meetingTime}
value={meeting}
onChange={(e) => {
setMeetingTime(parseInt(e.target.value));
setMeeting(e.target.value);
}}
onKeyDown={(event) => {
const keyValue = event.key;
@ -119,9 +117,9 @@ export default function NewTimeReport(): JSX.Element {
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={adminTime}
value={administration}
onChange={(e) => {
setAdminTime(parseInt(e.target.value));
setAdministration(e.target.value);
}}
onKeyDown={(event) => {
const keyValue = event.key;
@ -138,9 +136,9 @@ export default function NewTimeReport(): JSX.Element {
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={ownWorkTime}
value={ownwork}
onChange={(e) => {
setOwnWorkTime(parseInt(e.target.value));
setOwnWork(e.target.value);
}}
onKeyDown={(event) => {
const keyValue = event.key;
@ -157,9 +155,9 @@ export default function NewTimeReport(): JSX.Element {
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={studyTime}
value={studies}
onChange={(e) => {
setStudyTime(parseInt(e.target.value));
setStudies(e.target.value);
}}
onKeyDown={(event) => {
const keyValue = event.key;
@ -176,9 +174,9 @@ export default function NewTimeReport(): JSX.Element {
type="number"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={testingTime}
value={testing}
onChange={(e) => {
setTestingTime(parseInt(e.target.value));
setTesting(e.target.value);
}}
onKeyDown={(event) => {
const keyValue = event.key;

View file

@ -1,6 +1,6 @@
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
import TimeReport from "../../Components/NewWeeklyReport";
import TimeReport from "../../Components/TimeReport";
function PMTotalTimeActivity(): JSX.Element {
const content = (

View file

@ -1,6 +1,6 @@
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
import TimeReport from "../../Components/NewWeeklyReport";
import TimeReport from "../../Components/TimeReport";
function PMViewUnsignedReport(): JSX.Element {
const content = (

View file

@ -1,6 +1,6 @@
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
import NewTimeReport from "../../Components/NewWeeklyReport";
import NewTimeReport from "../../Components/TimeReport";
function UserEditTimeReportPage(): JSX.Element {
const content = (

View file

@ -1,6 +1,6 @@
import BasicWindow from "../../Components/BasicWindow";
import Button from "../../Components/Button";
import NewTimeReport from "../../Components/NewWeeklyReport";
import NewTimeReport from "../../Components/TimeReport";
import { Link } from "react-router-dom";
function UserNewTimeReportPage(): JSX.Element {

View file

@ -1,88 +0,0 @@
// Code generated by tygo. DO NOT EDIT.
//////////
// source: WeeklyReport.go
/**
* This is what should be submitted to the server, the username will be derived from the JWT token
*/
export interface NewWeeklyReport {
/**
* The name of the project, as it appears in the database
*/
projectName: string;
/**
* The week number
*/
week: number /* int */;
/**
* Total time spent on development
*/
developmentTime: number /* int */;
/**
* Total time spent in meetings
*/
meetingTime: number /* int */;
/**
* Total time spent on administrative tasks
*/
adminTime: number /* int */;
/**
* Total time spent on personal projects
*/
ownWorkTime: number /* int */;
/**
* Total time spent on studying
*/
studyTime: number /* int */;
/**
* Total time spent on testing
*/
testingTime: number /* int */;
}
//////////
// source: project.go
/**
* Project is a struct that holds the information about a project
*/
export interface Project {
id: number /* int */;
name: string;
description: string;
owner: string;
}
/**
* As it arrives from the client, Owner is derived from the JWT token
*/
export interface NewProject {
name: string;
description: string;
}
//////////
// source: users.go
/**
* User struct represents a user in the system
*/
export interface User {
userId: string;
username: string;
password: string;
}
/**
* Should be used when registering, for example
*/
export interface NewUser {
username: string;
password: string;
}
/**
* PublicUser represents a user that is safe to send over the API (no password)
*/
export interface PublicUser {
userId: string;
username: string;
}

View file

@ -1,97 +0,0 @@
import requests
import string
import random
def randomString(len=10):
"""Generate a random string of fixed length"""
letters = string.ascii_lowercase
return "".join(random.choice(letters) for i in range(len))
# Defined once per test run
username = randomString()
projectName = randomString()
# The base URL of the API
base_url = "http://localhost:8080"
# Endpoint to test
registerPath = base_url + "/api/register"
loginPath = base_url + "/api/login"
addProjectPath = base_url + "/api/project"
submitReportPath = base_url + "/api/submitReport"
# Posts the username and password to the register endpoint
def register(username: string, password: string):
print("Registering with username: ", username, " and password: ", password)
response = requests.post(
registerPath, json={"username": username, "password": password}
)
print(response.text)
return response
# Posts the username and password to the login endpoint
def login(username: string, password: string):
print("Logging in with username: ", username, " and password: ", password)
response = requests.post(
loginPath, json={"username": username, "password": password}
)
print(response.text)
return response
def test_login():
response = login(username, "always_same")
assert response.status_code == 200, "Login failed"
print("Login successful")
return response.json()["token"]
def test_create_user():
response = register(username, "always_same")
assert response.status_code == 200, "Registration failed"
print("Registration successful")
def test_add_project():
loginResponse = login(username, "always_same")
token = loginResponse.json()["token"]
response = requests.post(
addProjectPath,
json={"name": projectName, "description": "This is a project"},
headers={"Authorization": "Bearer " + token},
)
print(response.text)
assert response.status_code == 200, "Add project failed"
print("Add project successful")
def test_submit_report():
token = login(username, "always_same").json()["token"]
response = requests.post(
submitReportPath,
json={
"projectName": "report1",
"week": 1,
"developmentTime": 10,
"meetingTime": 5,
"adminTime": 5,
"ownWorkTime": 10,
"studyTime": 10,
"testingTime": 10,
},
headers={"Authorization": "Bearer " + token},
)
print(response.text)
assert response.status_code == 200, "Submit report failed"
print("Submit report successful")
if __name__ == "__main__":
test_create_user()
test_login()
test_add_project()
test_submit_report()