परीक्षण के लिए स्क्वायर रेट्रोफिट सर्वर मॉक


97

चौकोर रेट्रोफिट ढांचे का उपयोग करते समय परीक्षण के लिए सर्वर का मजाक उड़ाने का सबसे अच्छा तरीका क्या है ।

संभावित तरीके:

  1. एक नया रेट्रोफ़िट क्लाइंट बनाएं और इसे RestAdapter.Builder ()। SetClient () में सेट करें। इसमें रिक्वेस्ट ऑब्जेक्ट को पार्स करना और रिस्पॉन्स ऑब्जेक्ट के रूप में जसन को वापस करना शामिल है।

  2. इस एनोटेट इंटरफेस को एक मॉक क्लास के रूप में लागू करें और उस संस्करण का उपयोग RestAdapter.create () द्वारा अभिप्रेरित न करें (अभ्यस्त टेस्ट गॉन सीरियलाइज़ेशन)

  3. ?

आदर्श रूप से, मैं चाहता हूं कि नकली सर्वर जसन प्रतिक्रियाएं प्रदान करें ताकि मैं एक ही समय में गन्स के क्रमांकन का परीक्षण कर सकूं।

किसी भी उदाहरण की बहुत सराहना की जाएगी।


@ जेकवर्टन, इसका उद्देश्य क्या है square-oss? यह बेमानी लगता है retrofit
चार्ल्स

@ एलेक होम्स: क्या आपने अपनी समस्या का समाधान किया?
एंडीजी डिक

जवाबों:


104

परीक्षण के लिए मॉक रेट्रोफिट 2.0 अनुरोध

जैसा कि पुराने तंत्र MockClientवर्ग बनाने और इसे लागू करने से Clientअब रेट्रोफिट 2.0 के साथ काम नहीं कर रहे हैं, यहां मैं ऐसा करने का एक नया तरीका बताता हूं। अब आपको जो कुछ भी करने की आवश्यकता है, वह आपके कस्टम इंटरसेप्टर को OkHttpClient के लिए जोड़ना है, जैसा कि नीचे दिखाया गया हैFakeInterceptorक्लास सिर्फ interceptमेथड को ओवरराइड करता है और यदि आवेदन DEBUGमोड में है तो JSON दिया जाता है।

RestClient.java

public final class RestClient {

    private static IRestService mRestService = null;

    public static IRestService getClient() {
        if(mRestService == null) {
            final OkHttpClient client = new OkHttpClient();
            // ***YOUR CUSTOM INTERCEPTOR GOES HERE***
            client.interceptors().add(new FakeInterceptor());

            final Retrofit retrofit = new Retrofit.Builder()
                            // Using custom Jackson Converter to parse JSON
                            // Add dependencies:
                            // com.squareup.retrofit:converter-jackson:2.0.0-beta2
                    .addConverterFactory(JacksonConverterFactory.create())
                            // Endpoint
                    .baseUrl(IRestService.ENDPOINT)
                    .client(client)
                    .build();

            mRestService = retrofit.create(IRestService.class);
        }
        return mRestService;
    }
}

IRestService.java

public interface IRestService {

    String ENDPOINT = "http://www.vavian.com/";

    @GET("/")
    Call<Teacher> getTeacherById(@Query("id") final String id);
}

FakeInterceptor.java

public class FakeInterceptor implements Interceptor { 
    // FAKE RESPONSES.
    private final static String TEACHER_ID_1 = "{\"id\":1,\"age\":28,\"name\":\"Victor Apoyan\"}";
    private final static String TEACHER_ID_2 = "{\"id\":1,\"age\":16,\"name\":\"Tovmas Apoyan\"}";

    @Override
    public Response intercept(Chain chain) throws IOException {
        Response response = null;
        if(BuildConfig.DEBUG) {
            String responseString;
            // Get Request URI.
            final URI uri = chain.request().url().uri();
            // Get Query String.
            final String query = uri.getQuery();
            // Parse the Query String.
            final String[] parsedQuery = query.split("=");
            if(parsedQuery[0].equalsIgnoreCase("id") && parsedQuery[1].equalsIgnoreCase("1")) {
                responseString = TEACHER_ID_1;
            }
            else if(parsedQuery[0].equalsIgnoreCase("id") && parsedQuery[1].equalsIgnoreCase("2")){
                responseString = TEACHER_ID_2;
            }
            else {
                responseString = "";
            }

            response = new Response.Builder()
                    .code(200)
                    .message(responseString)
                    .request(chain.request())
                    .protocol(Protocol.HTTP_1_0)
                    .body(ResponseBody.create(MediaType.parse("application/json"), responseString.getBytes()))
                    .addHeader("content-type", "application/json")
                    .build();
        }
        else {
            response = chain.proceed(chain.request());
        }

        return response;
    }
}

GitHub पर परियोजना का स्रोत कोड


10
UnsupportedOperationException से बचने के लिए OkHttpClient.Builder का उपयोग करें। अंतिम OkHttpClient okHttpClient = new OkHttpClient.Builder () .addInterceptor (नया FakeInterceptor ()) .build ();
जॉन

4
मेरे पास दो समस्याएं हैं: 1- कोई uri()अंडर नहीं है chain.request().uri()(मैंने इसे String url = chain.request().url().toString();अपने मामले के अनुसार अलग किया है)। 2- मुझे मिल रहा है java.lang.IllegalStateException: network interceptor my.package.name.FakeInterceptor must call proceed() exactly once। मैंने इसके addNetworkInterceptor()बजाय इसे जोड़ा है addInterceptor()
हेसम

2
उपयोग chain.request ()। url ()। uri ();
अमोल गुप्ता

मैं httpClient.authenticator पद्धति का परीक्षण करने के लिए 401 त्रुटि का मजाक कैसे उड़ा सकता हूं? कोड डालने से "401" प्रमाणित विधि कॉल नहीं करती है। मैं इसे कैसे संभाल सकता हूं?
महदी

मैंने वेब एपिस को अगले स्तर तक मॉक करने के लिए नकली इंटरसेप्टर दृष्टिकोण लिया है और इसके लिए थोड़ा पुस्तकालय प्रकाशित किया है ताकि इसे और भी आसान और अधिक सुविधाजनक बनाया जा सके। देखें github.com/donfuxx/Mockinizer
donfuxx

85

मैंने विधि 1 को इस प्रकार आज़माया

public class MockClient implements Client {

    @Override
    public Response execute(Request request) throws IOException {
        Uri uri = Uri.parse(request.getUrl());

        Log.d("MOCK SERVER", "fetching uri: " + uri.toString());

        String responseString = "";

        if(uri.getPath().equals("/path/of/interest")) {
            responseString = "JSON STRING HERE";
        } else {
            responseString = "OTHER JSON RESPONSE STRING";
        }

        return new Response(request.getUrl(), 200, "nothing", Collections.EMPTY_LIST, new TypedByteArray("application/json", responseString.getBytes()));
    }
}

और इसका उपयोग करके:

RestAdapter.Builder builder = new RestAdapter.Builder();
builder.setClient(new MockClient());

यह अच्छी तरह से काम करता है और आपको वास्तविक सर्वर से संपर्क किए बिना अपने जसन स्ट्रिंग्स का परीक्षण करने की अनुमति देता है!


मैंने रिस्पॉन्स कंस्ट्रक्टर को अपडेट किया है जिसका उपयोग पुराने के रूप में किया गया था, जो कि IllegalArgumentException url == nullरिट्रोफिट 1.4.1 के साथ फेंक रहा था ।
डैन जे

1
इसके अलावा बिल्डर को एक समापन बिंदु जोड़ने की जरूरत है:builder.setEndpoint("http://mockserver.com").setClient(new MockClient());
कोडप्रोग्रेसन

मैंने URL अनुरोध के आधार पर परिसंपत्ति फ़ोल्डर में फ़ाइल से प्रतिक्रिया प्राप्त करने के लिए ऊपर दिए गए नकली क्लाइंट को बढ़ाया है।
praveena_kd

21
Retrofit 2 अब क्लाइंट लेयर के लिए OkHttpClient का उपयोग करता है, और यह कोड काम नहीं करता है। ¿किसी भी विचार कैसे एक OkHttpClient नकली बनाने के लिए? संभवतः यह सब विस्तार और इसे अधिलेखित करने के बारे में है, लेकिन मुझे यकीन नहीं है कि कैसे।
गिलर्मोएमप

1
क्या आप अपने उत्तर को Retrofit2 के आधार पर भी अपडेट कर सकते हैं? धन्यवाद
२०:२० पर हेसाम

20

अपने ऑब्जेक्ट्स के लिए JSON के डीरिएरलाइज़ेशन का परीक्षण (संभवतः के साथ TypeAdapters?) एक अलग समस्या की तरह लगता है जिसके लिए अलग यूनिट टेस्ट की आवश्यकता होती है।

मैं व्यक्तिगत रूप से संस्करण 2 का उपयोग करता हूं। यह टाइप-सेफ, रिफलेक्टर-फ्रेंडली कोड को हल करता है जिसे आसानी से डीबग किया और बदला जा सकता है। आखिरकार, यदि आप परीक्षण के लिए उनमें से वैकल्पिक संस्करण नहीं बना रहे हैं, तो आपके एपीआई को इंटरफेस के रूप में घोषित करना क्या अच्छा है! जीत के लिए बहुरूपता।

एक अन्य विकल्प जावा का उपयोग कर रहा है Proxy। यह वास्तव में है कि रेट्रोफिट (वर्तमान में) अपने अंतर्निहित HTTP इंटरैक्शन को कैसे लागू करता है। यह निश्चित रूप से अधिक काम की आवश्यकता होगी, लेकिन बहुत अधिक गतिशील मोक्स के लिए अनुमति देगा।


यह मेरा पसंदीदा तरीका भी है। यह बहुत सरल है डिबग के रूप में ऊपर कहा गया है तो प्रतिक्रिया शरीर के साथ सीधे निपटने के लिए। @alec यदि आप GSON क्रमांकन का परीक्षण करना चाहते हैं, तो json string में जनरेट / रीड-इन करें और deserialize करने के लिए gson ऑब्जेक्ट का उपयोग करें। सिर के नीचे मेरा मानना ​​है कि रिट्रोफिट वैसे भी क्या करता है।
लोशेग

@JakeWharton क्या आप इसका एक छोटा उदाहरण प्रदान कर सकते हैं? मुझे यह कल्पना करने में परेशानी हो रही है ... धन्यवाद!
चाचा_टैक्स



8

मैं एक वास्तविक सर्वर पर जाने से पहले एपीआई का मजाक उड़ाने के लिए Apiary.io का बहुत बड़ा प्रशंसक हूं

आप फ्लैट .json फ़ाइलों का उपयोग भी कर सकते हैं और उन्हें फ़ाइल सिस्टम से पढ़ सकते हैं।

आप सार्वजनिक रूप से सुलभ एपीआई जैसे ट्विटर, फ़्लिकर, आदि का उपयोग कर सकते हैं।

यहाँ रेट्रोफिट के बारे में कुछ अन्य बेहतरीन संसाधन दिए गए हैं।

स्लाइड्स: https://docs.google.com/pretation/d/12Eb8OPI0PDisCjWne9-0qlXvp_-R4HmqVCjigOIgwfY/edit#slide.id.p

वीडियो: http://www.youtube.com/watch?v=UtM06W51pPw&feature=g-user-u

उदाहरण परियोजना: https://github.com/dustin-graham/ucad_twitter_retrofit_sample


7

Mockery (अस्वीकरण: मैं लेखक हूं) को बस इसी कार्य के लिए डिज़ाइन किया गया था।

मॉकरी एक मॉकिंग / टेस्टिंग लाइब्रेरी है जो रेट्रोफिट के लिए अंतर्निहित समर्थन के साथ नेटवर्किंग परतों को मान्य करने पर केंद्रित है। यह किसी दिए गए Api के चश्मे के आधार पर JUnit परीक्षण को स्वतः उत्पन्न करता है। विचार को किसी भी परीक्षा को मैन्युअल रूप से लिखना नहीं है; न ही सर्वर प्रतिक्रियाओं का मजाक उड़ाने के लिए इंटरफेस को लागू करना।


7
  1. सबसे पहले, अपना Retrofit इंटरफ़ेस बनाएं।

    public interface LifeKitServerService {
        /**
         * query event list from server,convert Retrofit's Call to RxJava's Observerable
         *
         * @return Observable<HttpResult<List<Event>>> event list from server,and it has been convert to Obseverable
         */
        @GET("api/event")
        Observable<HttpResult<List<Event>>> getEventList();
    }
  2. अनुसरण में आपका अनुरोधकर्ता:

    public final class HomeDataRequester {
        public static final String TAG = HomeDataRequester.class.getSimpleName();
        public static final String SERVER_ADDRESS = BuildConfig.DATA_SERVER_ADDR + "/";
        private LifeKitServerService mServerService;
    
        private HomeDataRequester() {
            OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    //using okhttp3 interceptor fake response.
                    .addInterceptor(new MockHomeDataInterceptor())
                    .build();
    
            Retrofit retrofit = new Retrofit.Builder()
                    .client(okHttpClient)
                    .baseUrl(SERVER_ADDRESS)
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create(new Gson()))
                    .build();
    
            //using okhttp3 inteception to fake response.
            mServerService = retrofit.create(LifeKitServerService.class);
    
            //Second choice,use MockRetrofit to fake data.
            //NetworkBehavior behavior = NetworkBehavior.create();
            //MockRetrofit mockRetrofit = new MockRetrofit.Builder(retrofit)
            //        .networkBehavior(behavior)
            //        .build();
            //mServerService = new MockLifeKitServerService(
            //                    mockRetrofit.create(LifeKitServerService.class));
        }
    
        public static HomeDataRequester getInstance() {
            return InstanceHolder.sInstance;
        }
    
        public void getEventList(Subscriber<HttpResult<List<Event>>> subscriber) {
            mServerService.getEventList()
                    .subscribeOn(Schedulers.io())
                    .unsubscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(subscriber);
        }
    }
  3. यदि आप दूसरी पसंद का उपयोग करते हैं (मॉक सर्वर डेटा के लिए रेट्रोफ़िट इंटरफ़ेस का उपयोग करें), तो आपको मॉकट्रॉफ़िट की आवश्यकता है, कोड का उपयोग करें:

    public final class MockLifeKitServerService implements LifeKitServerService {
    public static final String TAG = MockLifeKitServerService.class.getSimpleName();
    private BehaviorDelegate<LifeKitServerService> mDelegate;
    private Gson mGson = new Gson();
    
    public MockLifeKitServerService(BehaviorDelegate<LifeKitServerService> delegate) {
        mDelegate = delegate;
    }
    
    @Override
    public Observable<HttpResult<List<Event>>> getEventList() {
        List<Event> eventList = MockDataGenerator.generateEventList();
        HttpResult<List<Event>> httpResult = new HttpResult<>();
        httpResult.setCode(200);
        httpResult.setData(eventList);
    
        LogUtil.json(TAG, mGson.toJson(httpResult));
    
        String text = MockDataGenerator.getMockDataFromJsonFile("server/EventList.json");
        if (TextUtils.isEmpty(text)) {
            text = mGson.toJson(httpResult);
        }
        LogUtil.d(TAG, "Text:\n" + text);
    
        text = mGson.toJson(httpResult);
    
        return mDelegate.returningResponse(text).getEventList();
    }

4. मेरा डेटा एसेट फ़ाइल (एसेट / सर्वर / EventList.json) से है, यह फ़ाइल सामग्री है:

    {
      "code": 200,
      "data": [
        {
          "uuid": "e4beb3c8-3468-11e6-a07d-005056a05722",
          "title": "title",
          "image": "http://image.jpg",
          "goal": 1500000,
          "current": 51233,
          "hot": true,
          "completed": false,
          "createdAt": "2016-06-15T04:00:00.000Z"
        }
      ]
    }

5. यदि आप okhttp3 इंटरसेप्टर का उपयोग कर रहे हैं, तो आपको इस तरह से स्व-परिभाषित इंटरसेप्टर की आवश्यकता है:

public final class MockHomeDataInterceptor implements Interceptor {
    public static final String TAG = MockHomeDataInterceptor.class.getSimpleName();

    @Override
    public Response intercept(Chain chain) throws IOException {
        Response response = null;

        String path = chain.request().url().uri().getPath();
        LogUtil.d(TAG, "intercept: path=" + path);

        response = interceptRequestWhenDebug(chain, path);
        if (null == response) {
            LogUtil.i(TAG, "intercept: null == response");
            response = chain.proceed(chain.request());
        }
        return response;
    }

    private Response interceptRequestWhenDebug(Chain chain, String path) {
        Response response = null;
        if (BuildConfig.DEBUG) {
            Request request = chain.request();
            if (path.equalsIgnoreCase("/api/event")) {
                //get event list
                response = getMockEventListResponse(request);
            }
    }

    private Response getMockEventListResponse(Request request) {
        Response response;

        String data = MockDataGenerator.getMockDataFromJsonFile("server/EventList.json");
        response = getHttpSuccessResponse(request, data);
        return response;
    }

    private Response getHttpSuccessResponse(Request request, String dataJson) {
        Response response;
        if (TextUtils.isEmpty(dataJson)) {
            LogUtil.w(TAG, "getHttpSuccessResponse: dataJson is empty!");
            response = new Response.Builder()
                    .code(500)
                    .protocol(Protocol.HTTP_1_0)
                    .request(request)
                    //protocol&request be set,otherwise will be exception.
                    .build();
        } else {
            response = new Response.Builder()
                    .code(200)
                    .message(dataJson)
                    .request(request)
                    .protocol(Protocol.HTTP_1_0)
                    .addHeader("Content-Type", "application/json")
                    .body(ResponseBody.create(MediaType.parse("application/json"), dataJson))
                    .build();
        }
        return response;
    }
}

6. आम तौर पर, आप कोड के साथ अपने सर्वर का अनुरोध कर सकते हैं:

mHomeDataRequester.getEventList(new Subscriber<HttpResult<List<Event>>>() {
    @Override
    public void onCompleted() {

    }

    @Override
    public void onError(Throwable e) {
        LogUtil.e(TAG, "onError: ", e);
        if (mView != null) {
            mView.onEventListLoadFailed();
        }
    }

    @Override
    public void onNext(HttpResult<List<Event>> httpResult) {
        //Your json result will be convert by Gson and return in here!!!
    });
}

पढ़ने के लिए धन्यवाद।


5

@ एलेक द्वारा उत्तर में जोड़ते हुए, मैंने अनुरोध URL के आधार पर सीधे एसेट फ़ाइल में टेक्स्ट फ़ाइल से प्रतिक्रिया प्राप्त करने के लिए मॉक क्लाइंट को बढ़ाया है।

भूतपूर्व

@POST("/activate")
public void activate(@Body Request reqdata, Callback callback);

यहाँ नकली ग्राहक, समझता है कि निकाले जा रहे URL को सक्रिय किया गया है और संपत्ति फ़ोल्डर में सक्रिय.txt नामक एक फ़ाइल की तलाश करता है। यह एसेट / एक्टिवटेक्स्ट फाइल से कंटेंट को पढ़ता है और इसे एपीआई के लिए प्रतिक्रिया के रूप में भेजता है।

यहाँ विस्तारित है MockClient

public class MockClient implements Client {
    Context context;

    MockClient(Context context) {
        this.context = context;
    }

    @Override
    public Response execute(Request request) throws IOException {
        Uri uri = Uri.parse(request.getUrl());

        Log.d("MOCK SERVER", "fetching uri: " + uri.toString());

        String filename = uri.getPath();
        filename = filename.substring(filename.lastIndexOf('/') + 1).split("?")[0];

        try {
            Thread.sleep(2500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        InputStream is = context.getAssets().open(filename.toLowerCase() + ".txt");
        int size = is.available();
        byte[] buffer = new byte[size];
        is.read(buffer);
        is.close();
        String responseString = new String(buffer);

        return new Response(request.getUrl(), 200, "nothing", Collections.EMPTY_LIST, new TypedByteArray("application/json", responseString.getBytes()));
    }
}

विस्तृत स्पष्टीकरण के लिए आप अपने ब्लॉग चेकआउट कर सकते हैं
http://www.cumulations.com/blogs/13/Mock-API-response-in-Retrofit-using-custom-clients


नमस्ते, जब मैं एक परीक्षण वर्ग का उपयोग कर रहा हूँ रब्रोइलेक्ट्रिक का उपयोग करके और मॉक क्लाइंट का उपयोग रेट्रोफ़िट एपीआई को मॉक करने के लिए, यह मुझे कोई प्रतिक्रिया नहीं दे रहा है। क्या आप मुझे ऐसा करने के लिए मार्गदर्शन कर सकते हैं।
डोरी

हाय @ डॉरी सुनिश्चित करें कि आपके पास URL समाप्त होने वाला भाग और संपत्ति फ़ोल्डर के अंदर फ़ाइल नाम है। उदाहरण के लिए, आपका URL नीचे के रूप में (Reftrofit का उपयोग करके) @POST ("/ redeemGyft") सार्वजनिक शून्य रिडीमगेट (@Body MposRequest reqdata, कॉलबैक / RedeemGyftResponse> कॉलबैक) कह सकता है; उसके बाद एसेट फोल्डर में फाइल का नाम रिडीमगेट है। txt
praveena_kd

मैंने अपनी MockClientफाइल में स्टेटिक फाइल का नाम दिया है , जिसमें रॉब्रोइलेक्ट्रिक का उपयोग करके टेस्ट क्लास लिखा है। लेकिन मुझे json फ़ाइल से कोई प्रतिक्रिया नहीं मिल रही है।
डॉरी

अगर आपने एसेट फ़ोल्डर के अंदर फाइल रखी है तो उसे उठा लेना चाहिए।
praveena_kd

1

JSONPlaceholder: परीक्षण और प्रोटोटाइप के लिए नकली ऑनलाइन रीस्ट एपीआई

https://jsonplaceholder.typicode.com/

ReqresIn: एक और ऑनलाइन रीस्ट एपीआई

https://reqres.in/

डाकिया नकली सर्वर

यदि आप अनुकूलित प्रतिक्रिया पेलोड का परीक्षण करना चाहते हैं, तो उपरोक्त दो आपकी आवश्यकता के अनुरूप नहीं हो सकते हैं, तो आप पोस्टमैन मॉक सर्वर की कोशिश कर सकते हैं। अपने स्वयं के अनुरोध और प्रतिक्रिया पेलोड को परिभाषित करने के लिए इसे स्थापित करना और लचीला करना काफी आसान है।

यहां छवि विवरण दर्ज करें https://learning.getpostman.com/docs/postman/mock_servers/intro_to_mock_servers/ https://youtu.be/shYn3Ys3ygE


1

रेट्रोफिट के साथ मॉकिंग एपि कॉल अब Mockinizer के साथ और भी आसान है जो MockWebServer के साथ काम करना वास्तव में सीधे आगे बढ़ाता है:

import com.appham.mockinizer.RequestFilter
import okhttp3.mockwebserver.MockResponse

val mocks: Map<RequestFilter, MockResponse> = mapOf(

    RequestFilter("/mocked") to MockResponse().apply {
        setResponseCode(200)
        setBody("""{"title": "Banana Mock"}""")
    },

    RequestFilter("/mockedError") to MockResponse().apply {
        setResponseCode(400)
    }

)

बस RequestFilter और MockResponses का एक नक्शा बनाएं और फिर इसे अपने OkHttpClient बिल्डर बिल्डर में प्लग करें:

OkHttpClient.Builder()
            .addInterceptor(loggingInterceptor)
            .mockinize(mocks) // <-- just plug in your custom mocks here
            .build()

आपको MockWebServer आदि को कॉन्फ़िगर करने के बारे में चिंता करने की ज़रूरत नहीं है। बस अपने मोक्स को जोड़ें बाकी सभी आपके लिए Mockinizer द्वारा किया जाता है।

(डिस्क्लेमर: मैं मॉकिनलाइज़र का लेखक हूं)


0

मेरे लिए कस्टम रेट्रोफिट क्लाइंट लचीलेपन के कारण बहुत अच्छा है। खासकर जब आप किसी भी डीआई ढांचे का उपयोग करते हैं तो आप तेजी से और सरल मोड़ को बंद / चालू कर सकते हैं। मैं डैगर द्वारा यूनिट और एकीकरण परीक्षणों में प्रदान किए गए कस्टम क्लाइंट का उपयोग कर रहा हूं।

संपादित करें: यहां आपको मॉकिंग रेट्रोफिट https://github.com/pawelByszewski/retrofitmock का उदाहरण मिलता है

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.