Rest API-test || Hurtig guide til RestAssured tutorial 2020

Rest Assured API-test til automatisering af Rest API-test

RestAssured - Den helt sikre tutorial-api-test
Vær sikker på API-automatisering

I denne udtømmende Rest Assured tutorial vil vi lære Rest API Testing i dybden, API Test Automation sammen med Rest Assured i modulær tilgang

Hvad er RestAssured og dets anvendelse

Rest Assured er en meget udbredt open source-teknologi til REST API Automation Testing, dette er baseret på java-baseret bibliotek.

Vær sikker på at interagerer med Rest API i en hovedløs klienttilstand, vi kan forbedre den samme anmodning ved at tilføje forskellige lag for at danne anmodningen og oprette HTTP-anmodning via forskellige HTTPS-verb til serveren.

Vær sikker indbygget i biblioteket giver enorme metoder og hjælpeprogrammer til at udføre valideringen af ​​det svar, der modtages fra serveren, såsom statusmeddelelse, statuskode og svartekst.

Denne komplette serie af Rest Assured Tutorial til REST API Automation Testing består af følgende emner:

Kom godt i gang: Konfiguration af restAssured med Build-værktøj, dvs. Maven / gradle

TRIN 1: Hvis du arbejder med maven, skal du blot tilføje følgende afhængighed i pom.xml (du kan også vælge en hvilken som helst anden version):

For at komme i gang med REST Assured skal du blot tilføje afhængigheden af ​​dit projekt. 


    io. forsikret
    stol trygt på
    4.3.0
    prøve

Hvis du arbejder med gradle, skal du blot tilføje følgende i build.gradle (igen kan du også vælge en hvilken som helst anden version):

testCompile gruppe: 'io.rest-assured', navn: 'rest-assured', version: '4.3.0'

TRIN 2: REST Assured kan integreres og bruges meget let med de eksisterende enhedstestrammer, dvs. Testng, JUnit

Her bruger vi testNg i henhold til Unit Test Framework.

Når først bibliotekerne med sikkerhed er importeret, skal vi tilføje følgende statiske import til vores testklasser:

importer statisk io.restassured.RestAssured. *;

importer statisk org.hamcrest.Matchers. *;

BEMÆRK : Til dette kommende læringsformål tester vi Ergast Developer API, som kan findes her. Denne API giver historiske data relateret til Formel 1 løb, kørere, kredsløb osv.

Kendskab til syntaksen:

Vær sikker på understøtter BDD-format (Agurkesyntaks) for at skrive testskripterne dvs. i givet / hvornår / derefter / og format, vi antager, at du har forståelse for BDD / agurk-syntaks, hvis ikke, vil vi foreslå at bruge 30 minutter på at forstå, hvad der er BDD (Agurkesyntaks) og hvordan fungerer det og dets meget grundlæggende.

T-01: Vores 1. script, der i grunden validerer antallet af kredsløb i F1 i 2017 ved hjælp af denne API (http://ergast.com/api/f1/2017/circuits.json)

@Test (beskrivelse = "Antal kredsløb i sæson 2017 skal være 20") offentlig ugyldig valideringNumberOfCircuits () {givet (). Når (). Get ("http://ergast.com/api/f1/2017/circuits. json "). derefter (). assertThat (). body ("MRData.CircuitTable.Circuits.circuitId", hasSize (20)); }

Validering af resten API-svar :

1. Fanger JSON-svar fra API-anmodningen.

2. Forespørgsler til circuitId ved hjælp af GPath-udtryk "MRData.CircuitTable.Circuits.circuitId"

3. Kontrollerer, at samling af circuitId-elementer har størrelsen 20

Her bruger vi Hamcrest matchere til forskellige valideringer såsom

  • equalTo () validerer ligestillingen
  • lessThan () og greaterThan () metoder til komparativ validering,
  • hasItem () -metoden bruges til at validere tilstedeværelsen af ​​et element fra en samling af elementer.

Der er forskellige andre metoder, som er nyttige til at udføre en vis validering.

Du kan desuden henvise til dokumentationen til Hamcrest-biblioteket for en komplet liste over matchere og metoder.

Validering af svarkode:

givet (). når (). get ("http://ergast.com/api/f1/2017/circuits.json"). derefter (). assertThat (). statusCode (200);

Validering af indholdstype

givet (). når (). get ("http://ergast.com/api/f1/2017/circuits.json") .then (). assertThat (). contentType (ContentType.JSON);

Validerende overskrift "Indholdslængde"

givet (). når (). get ("http://ergast.com/api/f1/2017/circuits.json") .then (). assertThat (). header (" Content-Length ", equalTo (" 4551 "));

Flere validering i en enkelt test som (ved hjælp af og () metoder):

@Test (beskrivelse = "Antal kredsløb i 2017-sæsonen skal være 20")
    offentlig ugyldighed validatingNumberOfCircuits () {
        givet (). når (). get ("http://ergast.com/api/f1/2017/circuits.json") .then (). assertThat (). header ("Content-Length", equalTo (" 4551 ")). Og (). StatusKode (200);
    }

Validerende svar Body element / attribut:

Vi kan bruge JsonPath til at hente værdien af ​​json-attributterne og sætte påstand ved hjælp af TestNg

@Test (beskrivelse = "Validering af serienavn, som er f1")
    offentlig ugyldighed validatingSeriesName () {
        // Konverter ResponseBody til streng
        String responseBody = given (). Når (). Get ("http://ergast.com/api/f1/2017/circuits.json") .getBody (). AsString ();
        // Opret JsonPath-objekt ved at videregive svarteksten som en streng
        JsonPath resJson = ny JsonPath (responsBody);
        // Hent attributværdi-serien under MRData
        String seriesName = resJson.getString ("MRData.series");
        // Bruger TestNg påstand
        Assert.assertEquals ("f1", seriesName);
    }

På samme måde kunne vi få værdien af ​​XML-svar ved hjælp af XMLPath. Her arbejder vi med JSON, derfor brugte vi her JSonPath

RESTful API'er understøtter kun to typer parametre:

A. Forespørgselsparametre: Her tilføjes parametre i slutningen af ​​API-slutpunktet og kan identificeres ved spørgsmålstegnet og danner et nøgleværdipar som f.eks. 

https://www.google.com/search?q=https://www.wikipedia.org/

Her i ovenstående API er 'q' parameteren, og 'https://www.wikipedia.org/' er værdien for denne parameter, hvis vi skal søge 'SOMETHING_ELSE_TEXT'vi kunne erstatte værdien af ​​parameteren 'q' med 'SOMETHING_ELSE_TEXT'i stedet for https://www.wikipedia.org/.

B. Sti-parametre: Disse er den del af RESTful API-slutpunkt. 

f.eks. slutpunkt, som vi brugte tidligere: http://ergast.com/api/f1/2017/circuits.json, her er “2017” en stiparameterværdi.

For at få et resultat for året 2016 kunne vi erstatte 2017 med 2016 så vil API'en give svarorganet for 2016.

Test med Path Params til RestAssured

@Test (beskrivelse = "Validering af antallet af kredsløb ved hjælp af Path Params")
    offentlig ugyldig testWithPathParams () {
        String seasonNumber = "2017";
       String responseBody = given (). PathParam ("season", seasonNumber) .when (). Get ("http://ergast.com/api/f1/{season}/circuits.json") .getBody (). AsString ();
        // Opret JsonPath-objekt ved at videregive svarteksten som en streng
        JsonPath resJson = ny JsonPath (responsBody);
        // Hent attributværdi-serien under MRData
        String seriesName = resJson.getString ("MRData.series");
        // Bruger TestNg påstand
        Assert.assertEquals ("f1", seriesName);
    }

Test ved hjælp af forespørgselsparametre til RestAssured

@Test (beskrivelse = "Validering af Google-søgning ved hjælp af Query Params")
    offentlig ugyldig testWithPathParams () {
        String searchItem = "https://www.wikipedia.org/";
  given (). queryParam ("q", searchItem) .when (). get ("https://www.google.com/search") .then (). assertThat (). statusCode (200);
    }

Parametreringstest:

Vi kan udføre datadrevet test (dvs. det samme testscript udføres flere gange med forskellige sæt inputdata og levere forskellige outputdata) ved hjælp af Rest Assured 

TRIN 1: Oprettet en testNg-dataudbyder.

TRIN 2: Brug dataudbyderen i test script.

@DataProvider (name = "seasonAndRaceNumbers")
    offentligt objekt [] [] testDataFeed () {
        returner nyt objekt [] [] {
                {"2017", 20},
                {"2016", 21}
        };
    }
@Test (beskrivelse = "Antal kredsløbsvalidering i forskellige årstider", dataProvider = "seasonAndRaceNumbers") public void circuitNumberValidation (String seasonYear, int raceNumbers) {given ().pathParam ("sæson", sæsonår). når (). get ("http://ergast.com/api/f1/{sæson}/circuits.json "). derefter (). assertThat (). body (" MRData.CircuitTable.Circuits.circuitId ", hasSize (race numre)); }

Arbejde med parametre med flere værdier med RestAssured 

Multiværdiparametre er de parametre, der har mere end en værdi pr. Parameternavn (dvs. en liste over værdier pr. ParamKey), vi kan adressere dem som nedenfor:

givet (). param ("paramKey", "paramValue1", "paramaValue2"). når (). get ("API URL");

Eller vi kunne forberede en liste og videregive listen som parameternes værdi som:

Liste paramValue = ny ny ArrayList ();
paramValue.add (“paramvalue1”);
paramValue.add (“paramvalue2);
givet (). param ("paramKey", paramValue). når (). get ("API URL");
Arbejde med cookie med RestAssured 
givet (). cookie ("cookieK", "cookieVal"). når (). get ("API URL");

Or 

Vi kan også angive en multi-værdi cookie her som:

givet (). cookie ("cookieK", "cookieVal1", "cookieVal2"). når (). get ("API URL");

Arbejde med overskrifter:

Vi kan specificere i en anmodning ved hjælp af header / headers som:

givet (). header ("headerK1", "headerValue1"). header ("headerK2", "headerValue2"). når (). get ("API URL");

Arbejde med indhold Type:

given (). contentType ("application / json"). når (). get ("API URL");

Or 

given (). contentType (ContentType.JSON) .when (). get ();

Mål svartiden:

long timeDurationInSeconds = get (“API URL”). timeIn (SECONDS);

Rest API-godkendelse

REST sikret understøtter forskellige godkendelsesordninger, f.eks. OAuth, fordøjelse, certifikat, form og forebyggende grundlæggende godkendelse. Vi kan enten indstille godkendelse for hver anmodning 

her er en prøveforespørgsel med det samme:

givet (). auth (). basic ("uName", "pwd"). når (). get ("URL") ..

På den anden side godkendelse og defineret i nedenstående tilgang til HTTP-anmodninger:

RestAssured.authentication = basic ("uName", "pwd");

Grundlæggende AUTH-typer:

Der er to typer grundlæggende godkendelse, "forebyggende" og "udfordret token grundlæggende godkendelse".

Forebyggende grundlæggende godkendelse:

Dette sender den grundlæggende godkendelsesoplysninger, selv før serveren giver et uautoriseret svar i visse situationer sammen med anmodningen udløst, hvilket reducerer omkostningerne ved at oprette en ekstra forbindelse. Dette er typisk mest forekommende situationer, medmindre vi tester serverens evne til at udfordre. 

F.eks.

givet (). auth (). præemptive (). basic ("uName", "pwd"). når (). get ("URL"). derefter (). statusCode (200);

Udfordret grundlæggende godkendelse

På den anden side vil "udfordret grundlæggende godkendelse" REST Assured ikke levere legitimationsoplysninger, medmindre serveren udtrykkeligt har bedt om det, dvs. serveren kaster det uautoriserede svar. Efter dette uautoriserede svar sender Rest-Assured en anden anmodning til serveren, som er Auth.

givet (). auth (). basic ("uName", "pwd"). når (). get ("URL"). derefter (). statusCode (200);

Fordøjelsesgodkendelse

Fra nu af betragtes kun "udfordret fordøjelsesgodkendelse". f.eks:

givet (). auth (). digest ("uName", "pwd"). når (). get ("URL"). derefter (). statusCode (200); 

Formulargodkendelse

Vi kunne hovedsagelig opnå dette i 3 forskellige tilgange afhængigt af applikationen / scenarierne:

Formulargodkendelse er en af ​​de meget populære over internettet, hvilket betyder, at en bruger indtaster sine legitimationsoplysninger, dvs. brugernavn og adgangskode via en webside og logger ind på systemet. Dette kan løses ved hjælp af dette 

givet (). auth (). form ("uName", "pWd").
når (). get ("URL");
derefter (). statusCode (200);

Selvom dette muligvis ikke fungerer, da det er optimalt, og det kan bestå eller mislykkes afhængigt af websidens kompleksitet. En bedre mulighed er at give disse detaljer, når du opretter formulargodkendelse i nedenstående tilgang:

givet (). auth (). form ("uName", "pwd", ny FormAuthConfig ("/ 'nævner her form handlingsnavn, som er en del af html-sidekoden under formtagget'", "uName", "pwd ")). når (). get (" URL "). derefter (). statusCode (200);

I denne tilgang behøver REST Assured internt ikke at udløse yderligere anmodning og parse gennem websiden. 

Hvis du i tilfælde af at du bruger standard Spring Security så en foruddefineret FormAuthConfig udløses.

givet (). auth (). form ("uName", "Pwd", FormAuthConfig.springSecurity ()). når (). get ("URL"). derefter (). statusCode (200);

BEMÆRK: Hvis vi vil sende yderligere inputdata sammen med formulargodkendelse, kunne vi skrive nedenstående:

givet (). auth (). form ("uName", "pwd", formAuthConfig (). withAdditionalFields ("firstInputField", "secondInputField"). ..

CSRF:

CSRF står for forfalskning på tværs af websteder.

I dag er det meget almindeligt, at serveren giver et CSRF-token med svaret for at undgå CSRF-sikkerhedsangreb. REST Assured understøtter dette ved at bruge og automatisk parser og levere CSRF-token. 

For at opnå dette er REST Assured nødt til at fremsætte en yderligere anmodning og parse (få positioner) af hjemmesiden.

Vi kan aktivere CSRF-support ved at skrive nedenstående kode:

givet (). auth (). form ("uName", "pwd", formAuthConfig (). withAutoDetectionOfCsrf ()). når (). get ("URL"). derefter (). statusCode (200);

Derudover for at hjælpe REST Assured og gøre parsingen mere fejlfri og robust, kan vi levere CSRF-feltnavnet (her antager vi, at vi bruger standardværdier for Spring Security, og at vi kunne bruge foruddefineret springSecurity FormAuthConfig):

givet (). auth (). form ("uName", "pwd", springSecurity (). medCsrfFieldName ("_ csrf")). når (). get ("URL"). derefter (). statusCode (200);

Som standard sendes CSRF-værdien som en formularparameter med anmodningen, men vi kan konfigurere til at sende den som en overskrift, hvis det er nødvendigt som nedenfor:

givet (). auth (). form ("uName", "pwd", springSecurity (). withCsrfFieldName ("_ csrf"). sendCsrfTokenAsHeader ()). når (). get ("URL"). derefter (). statusCode (200);

OAuth 1:

OAuth 1 kræver, at Scribe er i klassestien. For at bruge oAuth 1-godkendelse kan vi gøre:

givet (). auth (). oauth (..). når (). ..

OAuth 2:

givet (). auth (). oauth2 (accessToken) .when (). ..

I ovenstående tilgang vil OAuth2 accessToken blive betragtet som en overskrift. For at være mere eksplicit kan vi også gøre:

givet (). auth (). præemptive (). oauth2 (accessToken) .when (). ..

Videregivelse af fil, byte-array, inputstrøm eller tekst i anmodning:

Når du sender store mængder data til serveren, er det generelt en almindelig tilgang til at bruge formet datateknik med flere dele. Vær sikker på at give metoder kaldet multiPart, der giver os mulighed for at specificere en fil, byte-array, inputstrøm eller tekst, der skal uploades. 

givet (). multiPart (ny fil ("/ File_Path")). når (). post ("/ upload");

POST-anmodning om oprettelse med sikkerhed

Med POST- og PUT-anmodninger sender vi data til serveren og dens grundlæggende oprettelse af ressourcer / opdatering af ressourcer, du kan overveje dette som en skrive- eller opdateringshandling.

De data, der sendes til serveren i en POST-anmodning, sendes i kroppen af ​​HTTP-anmodning / API-opkald. 

Den type indhold eller data, der sendes, kan have forskelligt format afhængigt af API'en, dvs. XML, JSON eller et andet format, defineres af indholdstypeoverskriften. 

Hvis POST-kroppen består af JSON-dataene, er overskriften Content-Type applikation / json. Ligeledes for en POST-anmodning bestående af en XML, ville headeren Content-Type være af applikation / xml-typen.

Her er nedenstående kodestykke for det samme:

given (). contentType ("application / json"). param ("pk", "pv"). when (). body ("JsonPAyloadString"). post ("url"). derefter (). assertThat (). statusKode (200);

BEMÆRK: Der er forskellige måder, hvorpå vi kan sende nyttelast / anmodningslegemet inde i metoden "body" som String (som vist i ovenstående uddrag), JsonObject, som en fil osv. Osv.,

PUT-anmodning med sikkerhed:

given (). contentType ("application / json"). param ("pk", "pv"). when (). body ("JsonPAyloadString"). put ("url"). then (). assertThat (). statusKode (200);

Slet anmodning med Rest-Assured:

given (). contentType ("application / json"). param ("pk", "pv"). når (). delete ("url"). derefter (). assertThat (). statusCode (200);

Og på den måde kan vi oprette forskellige Rest API-opkald til forskellige API-verber (GET / POST / PUT / DELETE osv.)

Serialisering og deserialisering i Java:

Serialisering er en grundlæggende behandling eller konvertering af objekttilstanden til en byte-strøm. På den anden side behandler deserialisering i Java eller konverterer byte-strømmen til det faktiske Java-objekt i hukommelsen. Denne mekanisme bruges i vedholdenhed af objektet.

Nedenfor er blokdiagrammet for det samme 

Rest API-test || Hurtig guide til RestAssured tutorial 2020

Fordele ved serialisering

A. For at gemme / fastholde et objekts tilstand.

B. At flyde et objekt over et netværk.

Opnå serialisering med JAVA

For at opnå et Java-objekt, der kan serialiseres, er vi nødt til at implementere java.io.Serializable-grænsefladen.

ObjectOutputStream-klassen, der indeholder writeObject () -metoden, der er ansvarlig for serialisering af et objekt.

ObjectInputStream-klassen indeholder også en anden metode kaldet readObject (), som er ansvarlig for at deserialisere et objekt.

klasser, der implementerer java.io.Serialiserbar grænseflade, kan objektet kun serialiseres.

Serializable er bare en markørgrænseflade, og som andre markedsgrænseflader har den ikke noget datamedlem eller metode tilknyttet. Som bruges til at "markere" java-klasser, så objekter i disse klasser får bestemte muligheder. Som få andre markørgrænseflader er: - Klonbar og fjernbetjening osv.

BEMÆRKNINGER:

1. Hvis en overordnet klasse har implementeret en Serializable-grænseflade, er underordnet klasse ikke påkrævet for at implementere det samme, men omvendt er ikke relevant.

2. Kun ikke-statiske datamedlemmer gemmes med Serialization-processen.

3. Statiske data-medlemmer og også de forbigående data-medlemmer lagres ikke af Serialization. Så hvis vi ikke behøver at gemme den ikke-statiske datamedlems værdi, kan vi gøre den forbigående.

4. Konstruktør kaldes aldrig, når et objekt deserialiseres.

TRIN 1: Det første trin er grundlæggende oprettelsen af ​​en klasse, der implementerer Serializable-grænsefladen:

import java.io.Serialiserbar;
public class Dummy implementer Serializable {
    privat int i;
    private strengdata;
    public Dummy (int i, String data)
    {
        dette.i = i;
        this.data = data;
    }
}

TRIN 2: Opret en klasse for at serieisere den:

importere java.io.FileNotFoundException;
importere java.io.FileOutputStream;
importer java.io.IOException;
importere java.io.ObjectOutputStream;
offentlig klasse Serialize {
    public static void Serialization (Object classObject, String fileName) {
        prøve {
            FileOutputStream fileStream = ny FileOutputStream (filnavn);
            ObjectOutputStream objectStream = ny ObjectOutputStream (fileStream);
            objectStream.writeObject (classObject);
            objectStream.close ();
            fileStream.close ();
        } fange (FileNotFoundException e) {
            // TODO Auto-genereret fangstblok
            e.printStackTrace ();
        } fangst (IOException e) {
            // TODO Auto-genereret fangstblok
            e.printStackTrace ();
        }
    }
    offentlig statisk tomrum hoved (String [] args) {
        Dummy dummyObj = ny Dummy (10, "Lambda-geeks");
        Serialisering (dummyObj, "DummSerialized");
    }
}

TRIN 3: Når Step2 er gennemført med succes, vil du se en fil, der er oprettet med nogle data i den, disse data er grundlæggende serielle data fra objektmedlemmerne.

  Deserialisering med java:

Her er nedenstående kodestykke:

 offentligt statisk objekt DeSerialize (streng filnavn)
    {
        prøve {
            FileInputStream fileStream = ny FileInputStream (ny fil (filnavn));
            ObjectInputStream objectStream = ny ObjectInputStream (fileStream);
            Objekt deserializeObject = objectStream.readObject ();
            objectStream.close ();
            fileStream.close ();
            returner deserializeObject;
        } fange (FileNotFoundException e) {
            e.printStackTrace ();
        } fangst (IOException e) {
            e.printStackTrace ();
        } fangst (ClassNotFoundException e) {
            e.printStackTrace ();
        }
        returnere null;
    }

Driver Code går således:

 offentlig statisk tomrum hoved (String [] args) {
      / * Dummy dummyObj = ny Dummy (10, "Lambda-geeks");
        Serialisering (dummyObj, "DummSerialized");
        System.out.println ("--------------------------------------------------- ------------------------------- ");
      */
        Dummy deSerializedRect = (Dummy) DeSerialize ("DummSerialized");
        System.out.println ("Data fra serieliseret objekt" + deSerializedRect.print ());
        System.out.println ("--------------------------------------------------- ------------------------------- ");
    }

JSONPATH Mere syntaks / forespørgsel:

Lad os antage en JSON som nedenfor:

{
  "OrganizationDetails": "Dummy detaljer om organisationen",
  "Region": "Asien",
  "Emp-detaljer": [
    {
      "Org": "lambda-Geeks",
      "Information": {
        "Ph": 1234567890,
        "Tilføj": "XYZ",
        "Alder": 45
      }
    },
    {
      "Org": "lambda-Geeks-2",
      "Information": {
        "Ph": 2134561230,
        "Tilføj": "ABC",
        "Alder": 35
      }
    }
  ]
}

i ovenstående JSON kaldes OrganizationDetails & Region som bladknudepunkt, fordi de ikke har nogen yderligere underknudepunkter / -elementer, men som på den anden side Emp-detaljer, der har underknudepunkt, hvorfor det ikke kaldes bladknudepunkt.

Her, hvis vi prøver at få værdien af ​​OrganizationDetails, skal vi bruge:

$ .OrganizationDetails 
Dette vil resultere i:
 [
  "Dummy detaljer om organisationen"
]

Ligesom Wise for at hente dataene for regionen skal vi skrive:

$. Region 

Hvis vi vil finde værdien af ​​alder for den første medarbejder, kan vi skrive:

$ .Emp-detaljer [0] .Information.Alder
Dette vil resultere i:
[
  45
]

I alderen for den 2. medarbejder kunne vi skrive som

$ .Emp-detaljer [1] .Information.Alder
Dette vil resultere i: [35]

På denne måde kan vi finde ud af JsonPath-udtrykket / forespørgslen for at hente dataene for de respektive felter i JSON.

Om Debarghya

Rest API-test || Hurtig guide til RestAssured tutorial 2020Selv Debarghya Roy, jeg er ingeniørarkitekt, der arbejder med fortune 5-firma og en open source-bidragsyder, der har omkring 12 års erfaring / ekspertise inden for forskellige teknologistak.
Jeg har arbejdet med forskellige teknologier såsom Java, C #, Python, Groovy, UI Automation (Selenium), Mobile Automation (Appium), API / Backend Automation, Performance Engineering (JMeter, Locust), Security Automation (MobSF, OwAsp, Kali Linux , Astra, ZAP osv.), RPA, Process Engineering Automation, Mainframe Automation, Back End Development med SpringBoot, Kafka, Redis, RabitMQ, ELK stack, GrayLog, Jenkins og har også erfaring med Cloud Technologies, DevOps osv.
Jeg bor i Bangalore, Indien med min kone og har passion for blogging, musik, guitar og min livssyn er Uddannelse for alle, som fødte LambdaGeeks. Lad os oprette forbindelse via linket ind - https://www.linkedin.com/in/debarghya-roy/

en English
X