@ApiOperation(value = "Create Device (saveDevice) with credentials ",
            notes = "Create or update the Device. When creating device, platform generates Device Id as " + UUID_WIKI_LINK +
                    "Requires to provide the Device Credentials object as well as an existing device profile ID or use \"default\".\n" +
                    "You may find the example of device with different type of credentials below: \n\n" +
                    "- Credentials type: <b>\"Access token\"</b> with <b>device profile ID</b> below: \n\n" +
                    DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_ACCESS_TOKEN_DESCRIPTION_MARKDOWN + "\n\n" +
                    "- Credentials type: <b>\"Access token\"</b> with  <b>device profile default</b> below: \n\n" +
                    DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_ACCESS_TOKEN_DEFAULT_DESCRIPTION_MARKDOWN + "\n\n" +
                    "- Credentials type: <b>\"X509\"</b> with <b>device profile ID</b> below: \n\n" +
                    "Note: <b>credentialsId</b> -  format <b>Sha3Hash</b>, <b>certificateValue</b> - format <b>PEM</b> (with \"--BEGIN CERTIFICATE----\" and  -\"----END CERTIFICATE-\").\n\n" +
                    DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_X509_CERTIFICATE_DESCRIPTION_MARKDOWN + "\n\n" +
                    "- Credentials type: <b>\"MQTT_BASIC\"</b> with <b>device profile ID</b> below: \n\n" +
                    DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_MQTT_BASIC_DESCRIPTION_MARKDOWN + "\n\n" +
                    "- You may find the example of <b>LwM2M</b> device and <b>RPK</b> credentials below: \n\n" +
                    "Note: LwM2M device - only existing device profile ID (Transport configuration -> Transport type: \"LWM2M\".\n\n" +
                    DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_LVM2M_RPK_DESCRIPTION_MARKDOWN + "\n\n" +
                    "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Device entity. " +
                    TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
    @RequestMapping(value = "/device-with-credentials", method = RequestMethod.POST)
    @ResponseBody
    public Device saveDeviceWithCredentials(@Parameter(description = "The JSON object with device and credentials. See method description above for example.")
                                            @Valid @RequestBody SaveDeviceWithCredentialsRequest deviceAndCredentials) throws ThingsboardException {
        Device device = deviceAndCredentials.getDevice();
        DeviceCredentials credentials = deviceAndCredentials.getCredentials();
        device.setTenantId(getCurrentUser().getTenantId());
        checkEntity(device.getId(), device, Resource.DEVICE);
        return tbDeviceService.saveDeviceWithCredentials(device, credentials, getCurrentUser());
    }
@Test
    public void testSaveDeviceWithCredentials() throws Exception {
        String testToken = "TEST_TOKEN";

        Device device = new Device();
        device.setName("My device");
        device.setType("default");

        DeviceCredentials deviceCredentials = new DeviceCredentials();
        deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
        deviceCredentials.setCredentialsId(testToken);

        SaveDeviceWithCredentialsRequest saveRequest = new SaveDeviceWithCredentialsRequest(device, deviceCredentials);

        Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);

        Device savedDevice = readResponse(doPost("/api/device-with-credentials", saveRequest).andExpect(status().isOk()), Device.class);

        Device oldDevice = new Device(savedDevice);

        testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(),
                savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
                ActionType.ADDED);
        testNotificationUpdateGatewayNever();

        Assert.assertNotNull(savedDevice);
        Assert.assertNotNull(savedDevice.getId());
        Assert.assertTrue(savedDevice.getCreatedTime() > 0);
        Assert.assertEquals(savedTenant.getId(), savedDevice.getTenantId());
        Assert.assertNotNull(savedDevice.getCustomerId());
        Assert.assertEquals(NULL_UUID, savedDevice.getCustomerId().getId());
        Assert.assertEquals(device.getName(), savedDevice.getName());

        DeviceCredentials foundDeviceCredentials =
                doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);

        Assert.assertNotNull(foundDeviceCredentials);
        Assert.assertNotNull(foundDeviceCredentials.getId());
        Assert.assertEquals(savedDevice.getId(), foundDeviceCredentials.getDeviceId());
        Assert.assertEquals(DeviceCredentialsType.ACCESS_TOKEN, foundDeviceCredentials.getCredentialsType());
        Assert.assertEquals(testToken, foundDeviceCredentials.getCredentialsId());

        Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);

        savedDevice.setName("My new device");
        savedDevice = doPost("/api/device", savedDevice, Device.class);

        testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(), savedTenant.getId(),
                tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED);
        testNotificationUpdateGatewayOneTime(savedDevice, oldDevice);
    }